FreeOZ论坛

标题: 以Visitor设计模式应对需求变化 [打印本页]

作者: hoopoos    时间: 30-4-2009 10:45
标题: 以Visitor设计模式应对需求变化
网络上大多数的设计模式文章,都是采用了一些抽象的例子,今天我以实际的工作经验来描述一下在客户需求变化下如何通过良好的模式来应对。

假设我们已经有一个成熟的version或者milestone,这个版本通过了全部的unit test,qa test, cvs或者svn上已经tag过并且冻结,这时候,REQ提出一个新的需求,要求DEV增加,这时候我们有两个选择,一,checkout部分源码Class,修改后commit,产生这个class的一个新的revision;二,不改动任何现存Java代码,只增加一些类,修改一下XML配置文件。哪种方式更优越呢?显然,第二种方案不需要触动已有的测试封装过的类,是更高质量的做法。但是,不改动任何类就想增加新的功能,这是天方夜谭么?

如果我们采用了visitor pattern,就可以实现,见图一

当我们在开发一个包含logic的implementation class的时候,只需要让它继承一个
abstract class Visitable
public void accept(Visitor visitor)
{
visitor.visit(this);
}

这里相当于给现有的classl留了一道小门,为的就是应对将来可能的变化

那么,在最初的版本里面,client是这样调用logic class的
ConcreteClass c=new ConcreteClass ();
c.methodA();

现在,需求增加了,要求多一个methodB,传统的方式是修改ConcreteClass的源码,增加一个method叫methodB,现在我们不动ConcreteClass

ConcreteClass extends Visitable,所以我们用一个新的Visitor,通过预留的那个后门,来增加新的逻辑,这里就好像你是房子的主人,今天需要打扫房间,可是你今天有事要出门,那么你就请一个有你房子后门钥匙的朋友来替你打扫。
(注,由于java不能多继承,如果你的ConcreteClass已经继承了其他类,可以采用composite一个Visitable的子类的方式来变相完成这个多继承)

public class MethodBVisitor implements Visitor
public void visit(Visitable visitable)
{
   ConcreteClass c=(ConcreteClass)visitable;
   methodB(c);
}
private void methodB(ConcreteClass c)
{
   //call public method in ConcreteClass, here is the new logic!
}

client的调用方式改为
ConcreteClass c=new ConcreteClass ();
c.methodA();
Visitor v=new MethodBVisitor();
c.accept(v);//methodB implemented in MethodBVisitor instead of in ConcreteClass

需要注意的是,如果要让Visitor能充分的实现新的功能,需要ConcreteClass把所有Visitor可能需要用到的method和property(gettter, setter)都设成public或者friendly的,这就好比你请朋友来帮你打扫,如果卧室也需要打扫的话你还要给他卧室钥匙。

最后,再进一步,把Visitor的创建,改成基于xml的(或者采用Spring的BeanFactory)
<Visitors>
  <Visitor class="com.***.***.MethodBVisitor">
  <Visitor class="com.***.***.MethodCVisitor">
<Visitors>

Client调用代码改为

ConcreteClass c=new ConcreteClass ();
c.methodA();
List<Visitor> vList=XMLConfigFactory.load(VISITOR_CLASS_LIST); //create object base on reflection, similiar to Spring IoC
for(Visitor v:vList) c.accept(v);

修改后,如果需要增加一个visitor,只需要在xml配置里增加一行,class名设为新增加的visitor,我们就可以在只修改一个xml配置文件和增加一个class的情况下,完成一个新的方法,确保不影响现存代码的质量。这里体现了一个重要的OOAD原则,扩展而不是修改。

[ 本帖最后由 hoopoos 于 30-4-2009 12:12 编辑 ]
作者: coredump    时间: 30-4-2009 12:04
LZ举的例子很清晰易懂,我的感觉就是Visitor模式适合用在有一个稳定的接口或者数据结构的前提下,同时又有很多围绕使用这个接口/数据结构的相似行为的场景下。 比如,IDE中有一个版本控制的接口, 我们就可以通过Visitor来提供各种具体版本控制的实现。还有比如一些音频/视频解码器plugin的实现等等,用Visitor也很自然。

以前使用Ice的时候阅读Ice的Slice Parser源代码,大量使用了Visitor模式用来从一个统一的IDL解析器中扩展生成面向各种语言的Mapping, 每个具体的语言都有一组Visitors, 每个语法成为都有一个对应的Visitor接口, 比如ClassVisitor, InterfaceVisitor, 对应于Java的代码生成会有对应的JavaClassVisitor, JavaInterfaceVisitor。 整体代码阅读起来的确非常清晰, 更大的好处还有可以非常方便地扩展出来对新的语言的支持。
作者: sliuhao    时间: 30-4-2009 12:44
单分派做(双)多分派......使调用和被调用者解耦
另一个好处是使一方类结构稳定.....
作者: yuba    时间: 30-4-2009 13:06
MethodBVisitor中methodB的逻辑如果不能完全由ConcreteClass已有的public的method组合而来,则必然有部分应该属于ConcreteClass的逻辑写到了methodB中。

理想的情况是
private void methodB(ConcreteClass c)
{
   //call public method in ConcreteClass, here is the new logic!
  c.method1();
  c.method2();
... ...
  c.methodN();
}

但是如果可以这样的话,引入visitor意义就不大了,client中一样可以做到,因为新的逻辑只是已有逻辑的组合。

现实的情况往往是
private void methodB(ConcreteClass c)
{
   //call public method in ConcreteClass, here is the new logic!
  c.method1();
  method1(c); // ignore private implementation
  c.method2();
  method2(c); // ignore private implementation
... ...
  c.methodN();
  methodN(c); // ignore private implementation
}

这样的话,原本应该面向对象地实现到ConcreteClass中的逻辑被面向过程地实现到了MethodBVisitor的方法中。

如果我说的是事实,那么这种使用仅仅是权宜之计而已。以后有机会还是要重构这些失散的逻辑,使之回到ConcreteClass。

不仅如此,配合这个模式,单元测试覆盖到的methodB以及其依赖的mehthod1到N也将在以后的重构中被修改,而且要增加新的单元测试案例覆盖那些刚回家的逻辑。

只有这样,代码才不会因年久失修而腐烂。

如果可以预见上述的工作未必能够完成,我建议还是直接解冻代码做修改,或者推迟这个需求到下一个release。
作者: yuba    时间: 30-4-2009 13:09
2楼的例子更像是策略,而非访问者。
作者: yuba    时间: 30-4-2009 13:14
原则上说,3楼说的非常正确

但是不适合评价本例

从这部分代码可以看出
ConcreteClass c=new ConcreteClass ();
c.methodA();
List<Visitor> vList=XMLConfigFactory.load(VISITOR_CLASS_LIST);
for(Visitor v:vList) c.accept(v);

这个模式是通过外部的扩展使新的需求通过松耦的方式实现,原有的紧耦还是存在的。
作者: sliuhao    时间: 30-4-2009 13:21
原帖由 yuba 于 30-4-2009 13:06 发表
MethodBVisitor中methodB的逻辑如果不能完全由ConcreteClass已有的public的method组合而来,则必然有部分应该属于ConcreteClass的逻辑写到了methodB中。

理想的情况是
private void methodB(ConcreteClass c)
{ ...


Agree! 这回仔细看了,呵呵......
而且还有个问题, 如果仅仅是methodB 引用ConcreteClass c那还好, 如果不仅如此,就不好玩了....
作者: sliuhao    时间: 30-4-2009 13:24
原帖由 yuba 于 30-4-2009 13:14 发表
原则上说,3楼说的非常正确

但是不适合评价本例

从这部分代码可以看出
ConcreteClass c=new ConcreteClass ();
c.methodA();
List vList=XMLConfigFactory.load(VISITOR_CLASS_LIST);
for(Visitor v:vList ...

赫赫,谢谢。
我觉得这个例子不适合做VISITOR,没有解到耦,只是一个权宜之计, 长远看, 会把代码搞乱...., 所以这么一说....:-)
作者: black_zerg    时间: 30-4-2009 13:56
提示: 作者被禁止或删除, 无法发言 逻辑分散。vistor 权力过大,不过怎么说呢,管用就行。现在还有什么面向方向编程,动态语言也有点流行,在变动的世界中,管用的就是好办法。
补充一点,个人以为除非应用是那种plugin的架构, 如果只是CR,直接改代码,增加个新方法,看起来是个傻办法实际上最简单。除非不得已不要弄那么复杂,逻辑还分散,以后没法查。

[ 本帖最后由 black_zerg 于 30-4-2009 14:08 编辑 ]
作者: hoopoos    时间: 30-4-2009 14:09
标题: 回复 #4 yuba 的帖子
"如果要让Visitor能充分的实现新的功能,需要ConcreteClass把所有Visitor可能需要用到的method和property(gettter, setter)都设成public或者friendly的"

这句话已经表明,新的Visitor能够在完全利用ConcreteClass现存方法和属性的基础上,扩展新的功能,而这些扩展的功能,完全可以脱离ConcreteClass而存在,和ConcreteClass没有什么耦合的关系(ConcreteClass的所有方法所有属性Visitor都能访问),并且不局限于ConcreteClass的现有方法的组合。如果ConcreteClass实现的得当,需求只增加而不变化,这个类可以永远的冻结起来。

Visitor在这里应用的一个重要前提是:需求只增加而不变化。

“这样的话,原本应该面向对象地实现到ConcreteClass中的逻辑被面向过程地实现到了MethodBVisitor的方法中。
如果我说的是事实,那么这种使用仅仅是权宜之计而已。以后有机会还是要重构这些失散的逻辑,使之回到ConcreteClass。”

如果如你所说的这样,“原本应该面向对象地实现到ConcreteClass中的逻辑”,这个就不符合我在上面提到的背景,举个例子,你有一部手机,你现在想增加一个GPS功能,你可以把手机拆了,装个GPS芯片进去,你也可以用一根线或者蓝牙,连一个GPS模块,假设你对拆手机装手机没把握的话,是不是后面的方案更好呢?
作者: sliuhao    时间: 30-4-2009 14:10
原帖由 black_zerg 于 30-4-2009 13:56 发表
逻辑分散。vistor 权力过大,不过怎么说呢,管用就行。现在还有什么面向方向编程,动态语言也有点流行,在变动的世界中,管用的就是好办法。


vistor 权力过大
~~~~~~~~~~~~~
Yes....
面向方向编程/ AspectJ是个有趣的话题,呵呵....
作者: sliuhao    时间: 30-4-2009 14:12
原帖由 hoopoos 于 30-4-2009 14:09 发表
"如果要让Visitor能充分的实现新的功能,需要ConcreteClass把所有Visitor可能需要用到的method和property(gettter, setter)都设成public或者friendly的"

这句话已经表明,新的Visitor能够在完全利用ConcreteClas ...


关键是你是"手机"设计者,而非使用者....
作者: hoopoos    时间: 30-4-2009 14:21
原帖由 black_zerg 于 30-4-2009 13:56 发表
逻辑分散。vistor 权力过大,不过怎么说呢,管用就行。现在还有什么面向方向编程,动态语言也有点流行,在变动的世界中,管用的就是好办法。
补充一点,个人以为除非应用是那种plugin的架构, 如果只是CR,直接改代 ...


如果你有5000个类,以及相应5000个test case,直接修改代码提交,意味着5000个类要重新编译,5000个test case要重新运行,application 重新deploy, server 重启,ant可能会运行几个小时。

如果你只是增加几个类,你可以把这几个class编译出来的.class更新到jar里面,只运行几个test case,也许10分钟,你的server已经更新了,带着新的功能。

假如你是在一个小team里面做一个小项目,那么别管什么模式框架了,管用就行。但是如果你在一个上百人的team里做一个周期长达几年的产品,你必须为很多事情考虑,这也是最需要设计模式的场合。

再着重说明一下,Visitor模式适用于,以新的附加功能,扩展已经测试封装过的模块。如果新的功能是本来就属于旧模块的核心功能,只能说明最初的需求和设计是失败的,不完整的。仍然是上面那个手机的例子,如果要给一个手机增加一个发短信的功能,能用Visitor么?这本来就是手机的基本功能!
作者: hoopoos    时间: 30-4-2009 14:34
The OpenClosedPrinciple (OCP): "A reusable class should be open for extension, but closed for modification."

Design Pattern体现了OO Design Principles,但是很多时候开发者不能接受,因为他们觉得小题大做,我建议不论是不是接受Design Pattern,在自己的设计里面体现Design Principles还是很重要的,你可以有自己的模式,但是你最好能尊重这些Principles,有一天你加入一个高水平的大型团队,你就体会到尊重这些Principles给你和你的团队带来的收益。
作者: sliuhao    时间: 30-4-2009 14:42
LZ挺defensive的.....这样做技术就很无趣了.......
完..........
作者: black_zerg    时间: 30-4-2009 14:48
提示: 作者被禁止或删除, 无法发言 不明白为什么要重编译5000个类。你的新逻辑全放一个class里也没问题,只不过在切入的时候,直接修改原先的代码,切入这个新类。这就是基于一个change request.需要做的事情.

如果用这个vistor的模式来做需求变化,因为你的主类不知道那些将来会变,只能暴露很多内部方法给vistor,逻辑就很分散。越是大的项目,这么做的后果就越严重,因为你根本不知道那个vistor,谁做的vistor会破坏你的逻辑甚至整体的逻辑。

如果你的应用程序本身要求就是可扩充的,设计成plugin-in的架构,考虑到了逻辑暴露的问题,那就用这个也无妨。

关于test case,本身我对这个不怎么有爱,但是理论上每次新版本,你就是必须全部跑,没什么好折中,特别vistor这种,感觉有逻辑侵入风险,更必须全跑。

所以我的结论和你是相反的,就需求变化来说, 这vistitor模式比较适合快速开发。优点是一次更新的逻辑比较集中。但长期看会搅乱整个程序逻辑。做一次变动就造几个vistor,而且得确定这样能适合所有的变动需求,并不是那么有意义。

总之我觉得这个东西适合于插件系统,不适合用来做CR。理由就是cr的可能涉及面很广,而且可能次数很多,你用这个vistor以后根本维护不过来,最后代码逻辑很可能七零八落。

[ 本帖最后由 black_zerg 于 30-4-2009 14:54 编辑 ]
作者: hoopoos    时间: 30-4-2009 14:50
原帖由 sliuhao 于 30-4-2009 14:42 发表
LZ挺defensive的.....这样做技术就很无趣了.......
完..........


呵呵,用a matter of fact说话,也是defensive么?如果你不同意我的看法,可以用一些事实来支持。我尽量清楚的表述前因后果,场景场合,以免误解。

做技术么,就是很客观的事情,如果我要defend我的观点,我必须用事实说话,"A reusable class should be open for extension, but closed for modification." ,why? 因为实践出真知。
作者: coredump    时间: 30-4-2009 14:53
基本上,LZ对Visitor是怎么回事解释的很清楚,但是举的例子不是体现Visitor优势的好例子。LS几位不需用放大镜盯着例子看,那样就更没趣了

tips:
讨论问题注意风度,避免文人相轻的坏习气
作者: coredump    时间: 30-4-2009 14:57
原帖由 black_zerg 于 30-4-2009 14:48 发表
总之我觉得这个东西适合于插件系统,不适合用来做CR


说到点子上了,LZ的原理解释方面可以打8分,例子只能打3分,因为有误导之嫌
作者: hoopoos    时间: 30-4-2009 15:02
原帖由 black_zerg 于 30-4-2009 14:48 发表
不明白为什么要重编译5000个类。你的新逻辑全放一个class里也没问题,只不过在切入的时候,直接修改原先的代码,切入这个新类。这就是基于一个change request.需要做的事情.

如果用这个vistor的模式来做需求变化, ...


一般来说,在大的team里面,做编译build的,是单独的一个人,他可能根本不知道你改了什么,他只知道,你改的这个东西,别的地方有调用,他不看代码的,所以他只能把所有的类全部编译测试。如果你用visitor,增加一个新的类,这个类只调用那个现存的测试封装过的类,我可以不可以假设,要编译测试的,只是新的这个visitor呢?因为无论这个visitor做了什么,它都不会影响现有的类,它就算全是bug,甚至编译都通不过,也只是它一个类的问题。

另外,visitor毕竟只是客人,他不能代替主人的事情,把主人该做的事情放到客人那里,然后责怪这个visitor模式造成逻辑分散,这合理么?

总的来说,模式合理的运用,是基于对场合的正确理解和分析,不分场合用模式,然后把自己陷于一个两难的境地,是不可取的,如果因为这个原因低估模式的价值,那更不取。
作者: coredump    时间: 30-4-2009 15:05
原帖由 yuba 于 30-4-2009 13:09 发表
2楼的例子更像是策略,而非访问者。


Visitor是双分派,Strategy是单分派。Visitor从概念的范围讲可以涵盖Strategy,同一类Visitor接口的不同实现可以采用Strategy。
作者: hoopoos    时间: 30-4-2009 15:05
原帖由 coredump 于 30-4-2009 14:57 发表


说到点子上了,LZ的原理解释方面可以打8分,例子只能打3分,因为有误导之嫌


我用了两个例子,手机GPS模块和主人请客人来帮忙打扫,不知道为什么会有误导之嫌? 请指正。
作者: black_zerg    时间: 30-4-2009 15:06
提示: 作者被禁止或删除, 无法发言 如果我没记错,ant根本不会编译你没改过的。
测试的话,要么别跑,要跑就得全跑。
打包的话都是做ant或者mvn脚本,跟人也没关系,谁打包,什么时候打包都一样逻辑啊。

[ 本帖最后由 black_zerg 于 30-4-2009 15:07 编辑 ]
作者: coredump    时间: 30-4-2009 15:12
原帖由 hoopoos 于 30-4-2009 15:05 发表


我用了两个例子,手机GPS模块和主人请客人来帮忙打扫,不知道为什么会有误导之嫌? 请指正。


说例子不恰当是指你1楼的例子,后面的例子还是很恰当的。

只是新的这个visitor呢?因为无论这个visitor做了什么,它都不会影响现有的类,它就算全是bug,甚至编译都通不过,也只是它一个类的问题


当这样的类累计起来后就不是一个类的问题了。 你强调的是一次的行为, black_zerg关注的是这种的行为的累积后果。
作者: yuba    时间: 30-4-2009 15:15
原帖由 coredump 于 30-4-2009 12:04 发表
LZ举的例子很清晰易懂

原帖由 coredump 于 30-4-2009 14:57 发表
例子只能打3分,因为有误导之嫌


一针见血

我就是因为很清晰易懂才被误导的
作者: hoopoos    时间: 30-4-2009 15:28
“假设我们已经有一个成熟的version或者milestone,这个版本通过了全部的unit test,qa test, cvs或者svn上已经tag过并且冻结,这时候,REQ提出一个新的需求,要求DEV增加,”

我想我已经把场合说的很清楚了

The OpenClosedPrinciple (OCP): "A reusable class should be open for extension, but closed for modification."

这个原则极致化的结果就是,不论是需求增加还是需求修改,永远只增加类,不会修改类,逻辑分散是一件坏事情么?Head First In Design Pattern里的第一个例子, 一个Duck类,飞的逻辑也分出去了,叫的逻辑也分出去了

这个讨论很好,大家都理解怎么回事的,不过个人的工作领域不一样,不一定能产生共鸣的。
作者: coredump    时间: 30-4-2009 15:35
这个原则极致化的结果就是,不论是需求增加还是需求修改,永远只增加类,不会修改类


这个极至化很傻
作者: hoopoos    时间: 30-4-2009 15:46
原帖由 coredump 于 30-4-2009 15:35 发表


这个极至化很傻



目前如果有人想做到这个极致化,确实很傻,但是,The OpenClosedPrinciple (OCP): "A reusable class should be open for extension, but closed for modification." (我已经记不清这是第几次引用这个了),能够在OO的领域提出这样的一个原则,说明这个极致化,是符合OO精神的,代表着更高质量的代码。无论是修改还是增加新功能,只增加新的类,不修改旧的类,旧的类一旦单元测试通过就永久冻结,新增加的类测试通过后,也冻结,这样增量的开发模式,使得推进的步伐更坚定,软件的质量更可靠。当然,极致化的前提是,架构的水平足够高,设计的水平足够高,需求分析的水平足够高。
作者: yuba    时间: 30-4-2009 15:58
一直以为主人只是不在家

是等他回来呢好呢?还是找有钥匙的朋友好呢?

现在才知道,主人不是不在家,是不在人世了
作者: coredump    时间: 30-4-2009 16:14
标题: 回复 #28 hoopoos 的帖子
你这样的观点完全的是钻牛角尖了,别忘了还有重构这一说
作者: hoopoos    时间: 30-4-2009 16:30
原帖由 coredump 于 30-4-2009 16:14 发表
你这样的观点完全的是钻牛角尖了,别忘了还有重构这一说


不太明白为什么会引起你这样的看法,我说这是极致化,本身就肯定在现在的大多数项目里面是做不到的,因为最初的设计分析水平就有限,但是这是趋势,为什么重构?是因为,原先的设计和框架存在问题,和先进的OO原则冲突。重构是修补,但不是必须的。

假设一个项目经历这样的生命周期,类只增加不修改,那么,质量是可控制的,如果在某个节点,有若干个BUG,至少可以大部分认定,这些是最近新增加的class造成的,原先的是可靠的。100%的按照这个原则去做,我也同意你的说法,很傻很天真,为何我就钻牛角尖了?
作者: yuba    时间: 30-4-2009 16:30
原帖由 coredump 于 30-4-2009 16:14 发表
别忘了还有重构这一说


亡我之心不死啊

再加上一条:

销毁源代码
作者: coredump    时间: 30-4-2009 16:33
标题: 回复 #31 hoopoos 的帖子
按照你这个说法,我啥OO也不要,就要个VCS就行了,某个BUG肯定是某个changeset导致的,代码随便写

ok,不是你钻牛角尖,是我钻牛角尖了,以至于我以为你和我一起钻了
作者: hoopoos    时间: 30-4-2009 16:33
原帖由 yuba 于 30-4-2009 15:58 发表
一直以为主人只是不在家

是等他回来呢好呢?还是找有钥匙的朋友好呢?

现在才知道,主人不是不在家,是不在人世了


你这个回复完全牛头不对马嘴,一个谈技术的帖子,也能有这样的回复,真是可笑。如果你是想嘲笑我,那我告诉你,你没资格。
作者: coredump    时间: 30-4-2009 16:41
讨论技术,不要抱着太这强烈的个人情绪。再次提醒,大家注意风度。

每个人的个人经历,性格都很不同,对技术的见解和观察角度也很不一样,希望大家在这里的探讨能够互相取长补短就好。相信大家都是很有素质的人,不要坏了这样纯技术探讨的气氛,谢谢。
作者: hoopoos    时间: 30-4-2009 16:42
原帖由 coredump 于 30-4-2009 16:33 发表
按照你这个说法,我啥OO也不要,就要个VCS就行了,某个BUG肯定是某个changeset导致的,代码随便写

ok,不是你钻牛角尖,是我钻牛角尖了,以至于我以为你和我一起钻了


你还没理解我的意思,并不是说要查哪个BUG是什么造成的,而是因为模块化的需要,质量的需要,我请问:你的手机要加GPS功能,你是愿意拆开加芯片改电路好还是买个蓝牙模块好?你可以选择前一个方案,但是容易把手机弄坏。
作者: coredump    时间: 30-4-2009 16:45
标题: 回复 #36 hoopoos 的帖子
参见#24和#35
作者: hoopoos    时间: 30-4-2009 16:51
标题: 回复 #37 coredump 的帖子
很好, 到了下班的时间了。

明白你的意思,行业和技术background不同的人不大可能对事物有一致的看法,reasonable。一般来说只有同一家公司同一个team的人最容易对一些东西产生共鸣和共识。

至于个人情绪么,只要是就技术论技术我总是欢迎讨论的,可惜总有些人文不对题,旁敲侧击。
作者: yuba    时间: 30-4-2009 18:33
标题: 回复 #34 hoopoos 的帖子
主人,朋友,钥匙的说法都取自一楼

如果即使主人回来了,也不能改变(修改)什么,和出不出门没有什么关系。所以不用因为“今天有事要出门”,才需要“有你房子后门钥匙的朋友”。

代码“永久冻结”了,不发展了,和不在人世有什么区别?后面的销毁源码是同样的意思。

本以为“很清晰易懂”,确还是让你误会了
作者: yuba    时间: 30-4-2009 18:45
原帖由 hoopoos 于 30-4-2009 16:33 发表
如果你是想嘲笑我,那我告诉你,你没资格。


刚看到这句

我没有嘲笑任何人,上面的解释希望你和大家都能看懂

我一直以为无论如何都不能嘲笑他人

但好像我错了,因为你试图告诉我,具备一定的“资格”的人是可以嘲笑你的

不过我保证,即使我具备了你所说的那个“资格”,我也不会嘲笑任何人的,包括你

谢谢你提醒我
作者: yuba    时间: 30-4-2009 19:24
讨论似乎转到“是否可以修改现有代码”的问题上了

看来认为“代码可以修改,味道不能忍受”的人比“代码不可修改,味道可以忍受”的人多

也许这正是软件发展缓慢的原因吧

想起了一个关于计算机和数学的关系的热帖

呵呵
作者: hoopoos    时间: 30-4-2009 19:31
标题: 回复 #40 yuba 的帖子
抱歉,是我没看懂,因为我从你的回复里没看出你看懂我的解释,有点绕口。

可能我的表达能力有问题,或者我太多假设了,向您道歉,多多包涵。
作者: yuba    时间: 30-4-2009 19:42
原帖由 hoopoos 于 30-4-2009 19:31 发表
我从你的回复里没看出你看懂我的解释


版主都说“很清晰易懂”了,你还是觉得我看不懂

这是谁嘲笑谁啊?!
作者: hoopoos    时间: 30-4-2009 20:40
原帖由 yuba 于 30-4-2009 19:42 发表


版主都说“很清晰易懂”了,你还是觉得我看不懂

这是谁嘲笑谁啊?!


呵呵,我说的是你没看懂我后面的解释,不是说你没看懂我的发贴,误会

总之,可能我的表达能力有限,或者理解能力也有限,交流有问题。
作者: black_zerg    时间: 30-4-2009 21:08
提示: 作者被禁止或删除, 无法发言 如果是谈“是否可以修改现有代码”,其实我是非常不赞成修改已有代码。理想的状态就是面向接口的模块化,如果一块有错,全部替换。另一个策略就是只增不减(增加新方法也算只增不减),  这其实是很实际的办法也是比较实用的做CR的原则。我以前做BOSS的时候记得团队就是遵守这个原则,结果就是一大堆垃圾方法哈哈,不过谁也不敢修改现有的方法,因为做CR的只有时间关注这一次的需求,谁也不高兴担那个风险。只增不减还是很简单实用的原则,虽然最终可能会导致代码腐败。在现实社会里这个就是我多年CR的基本原则哈,要么就重写,要么就只增不减,这样恢复都容易些。
补充一下,在需求变化的这个情况下,之所以不支持用visitor实现这个只增不减的原则,是因为这个原则只是个适用于快速开发的原则,本身就会降低代码质量,用了visitor后,首先并不一定适用所有的CR,又增加了一定的复杂度,分散了逻辑,却并没有带来明显的好处。说简单点,加个新方法起码还看得见找的到,你加几个新类再配置几下,以后的人就不知道到哪里去找了。几十次cr也是很常见的,你增加了几十次*n的新类后,到时候后来的人看着代码就可能要哭了。xml配置这些并不是一个CR所需要的东西

可能楼主的这个“需求变化”并不是我所理解的changeRequest, 而是指的是 这个系统需要设计为需要不断扩展功能, 那就是一个插件扩展机制,那就是一个很好的应用场景。或者你想表达的是保持接口,替换实现这个策略,如果是那样的话,直接写个新实现类用spring注入一下。只修改实现,保持接口稳定还是很重要的。

[ 本帖最后由 black_zerg 于 30-4-2009 21:39 编辑 ]
作者: key    时间: 30-4-2009 21:09
hoo的一楼写得很好。我在实际项目中遇过类似的问题,很感谢你给我指点。
作者: key    时间: 30-4-2009 21:25
原帖由 yuba 于 30-4-2009 19:24 发表
讨论似乎转到“是否可以修改现有代码”的问题上了

看来认为“代码可以修改,味道不能忍受”的人比“代码不可修改,味道可以忍受”的人多

也许这正是软件发展缓慢的原因吧

想起了一个关于计算机和数学的关系 ...


这是两个角度的问题,不能说绝对的错或对吧。
Refactor这本书一开始就教人要把代码改来改去,但事实上,写Refactor的那个人是做软件咨询的,
别人不改代码,他就没饭吃了……

而在工作环境里,如果有一个相对稳定的版本,然后把所有的.class打包,封起来,
然后重用,扩展,这是很有意义的事。

从长远来看,代码最好能做一些必要的refactor……这个观点不能说错,但要多长远呢?
楼主一开始就说了是在“扩展”。我觉得如果这个扩展特别的有意义,而又没有绝对的必要
加到原来的类里面,为什么一定要Refactor原来的类呢?如果一定要说这样就是做plug-in,
那就只好plugin了……因为的确这就是在扩展嘛。

我之所以支持楼主,更重要原因是,我明白到打开一个原始类,加一行代码的代价的确很大。
可能其他人所在的公司很好,没有遇到我这样的问题也不奇怪。我并没有贬低其他人的意思。
作者: yuba    时间: 30-4-2009 21:38
标题: 回复 #45 black_zerg 的帖子
严重同意,深有同感

打工的都该这么想,有时真把自己当葱了,代码又不是我的

只有代码不断腐烂,我们才会一直有饭碗

而代码的腐烂有两种情况,一种是质量下降了,充满味道,另一种是找不到owner了,没人敢动

有些更甚,我见过连log4j的新版本都不敢用。固件还升级呢,这简直是在做硬件
作者: yuba    时间: 30-4-2009 21:43
原帖由 key 于 30-4-2009 21:25 发表
Refactor这本书一开始就教人要把代码改来改去,但事实上,写Refactor的那个人是做软件咨询的,
别人不改代码,他就没饭吃了……


没有他估计大家还动不了肝火
作者: key    时间: 30-4-2009 22:16
原帖由 yuba 于 30-4-2009 21:38 发表
严重同意,深有同感

打工的都该这么想,有时真把自己当葱了,代码又不是我的

只有代码不断腐烂,我们才会一直有饭碗

而代码的腐烂有两种情况,一种是质量下降了,充满味道,另一种是找不到owner了,没人敢动 ...


我觉得更多时间我们有点过份学究,“完美主义”。事实上,如果一定要从完美的角度看问题,怎样都会看出毛病的。
你看design pattern出来后就马上有over design,而再细看一下,会发现design pattern很多地方都很不oo,
然后又怀疑起来。再看RoR之流,还AOP之流,有哪个OO了?

说得太远了。但总而言之,我觉得楼主对于visitor以及例解都说得很好,我愿意给9.5分(10分满分)。
作者: yuba    时间: 30-4-2009 22:45
标题: 回复 #50 key 的帖子
作为成熟的软件从业人员,大家都有自己的侧重和坚持而已

你不认为楼主的“极致化”也是一种“过分学究”和“完美主义”,我也没有办法

AOP是AO,本来就不OO

Ruby连字面常量都有方法,还不够OO吗

不想抬杠

作为成熟的软件从业人员,大家都有自己的侧重和坚持而已
作者: hoopoos    时间: 1-5-2009 09:24
“作为成熟的软件从业人员,大家都有自己的侧重和坚持而已”

非常赞同!
作者: ingeer    时间: 1-5-2009 09:48
Design Pattern 這東西讓沒經驗的新手看,看上一百年也不會懂的,這東西是要靠多做自己摸出來後,到這本書裏去較對一下,不過說實在太教條了  每個人都有自己的習慣愛好,CODING 還是要有點FLEXIBILITY的。
抓到老鼠的就是好貓,不過別忘了老鼠抓光了,貓也會被掃地出門,所以嘛。。 CODING還是不要做得太好。。哈哈
作者: key    时间: 1-5-2009 16:43
原帖由 ingeer 于 1-5-2009 09:48 发表
Design Pattern 這東西讓沒經驗的新手看,看上一百年也不會懂的

不同的阶段看有不同的得益。新手学习,难免有点穿凿附会,但即使这样,得益还是很大的。

這東西是要靠多做自己摸出來後,到這本書裏去較對一下

有些部分能摸出来,有的就摸不了。

不過說實在太教條了  每個人都有自己的習慣愛好,CODING 還是要有點FLEXIBILITY的

想省时省力,还是少点flexibility好一点。coding不是一个创意工业。
如果想发挥创造力,最好还是在architecture上下功夫。coding上做文章……
不是不行,而是一般人没有这个精力,达不到这个高度,最后就只是在浪费时间。

不過別忘了老鼠抓光了,貓也會被掃地出門,所以嘛。。 CODING還是不要做得太好

这个很认同。我自己程序最大的缺点就是没有什么bug,后期维护接近0。。。。
作者: yuba    时间: 1-5-2009 17:14
原帖由 key 于 1-5-2009 16:43 发表
我自己程序最大的缺点就是没有什么bug,后期维护接近0


接近0不算啥

按照楼主的想法做,你的程序的后期维护等于0
作者: ingeer    时间: 7-5-2009 08:41
原帖由 key 于 1-5-2009 16:43 发表

不同的阶段看有不同的得益。新手学习,难免有点穿凿附会,但即使这样,得益还是很大的。


有些部分能摸出来,有的就摸不了。


想省时省力,还是少点flexibility好一点。coding不是一个创意工业。
如果想发 ...



牛,您真牛,請繼續牛,咱是代碼民工,看個熱鬧
作者: hoopoos    时间: 7-5-2009 09:21
谢谢大家热烈的参与讨论,再友情提示一下,模式要活学活用,注意场合。我这里提Visitor模式不是说一有需求变化就用Visitor,是在特定的场合,已经完成了一个稳定的模块,冻结,打包,发布,这样的前提下,为了降低改动带来的风险,保持代码的integrity,采用的一种灵活策略。如果处在开发生命周期的当中,代码不稳定,频繁的需要修改,肯定是不能用Visitor的。
作者: key    时间: 7-5-2009 22:49

原帖由 ingeer 于 7-5-2009 08:41 发表



牛,您真牛,請繼續牛,咱是代碼民工,看個熱鬧


您老牛一点,俺才是代码民工。你提倡flexible,俺提倡跟着别人屁股跑,高下之分,一目了然嘛

作者: caoglish    时间: 22-12-2013 00:15
本帖最后由 caoglish 于 22-12-2013 01:17 编辑

  1. var visitable = function () {
  2.     this.accept = function (visitor) {
  3.         visitor.visit(this);
  4.     };
  5. };
  6. element = new visitable();
  7. element.name = "John";


  8. visitor = {
  9.     visit: function (visitable) {
  10.         console.log(visitable.name);
  11.     }
  12. };

  13. element.accept(visitor);


  14. visitor = {
  15.     visit: function (visitable) {
  16.         console.log("name:" + visitable.name);
  17.     }
  18. };

  19. element.accept(visitor);

  20. visitor = {
  21.     visit: function (visitable) {
  22.         console.log("First Name:" + visitable.name);
  23.     }
  24. };

  25. element.accept(visitor);


复制代码
jsbin

Javascript 实现的例子
作者: caoglish    时间: 22-12-2013 00:40
本帖最后由 caoglish 于 22-12-2013 01:43 编辑
  1. var Client = function () {
  2.   this.setStrategy=function(strategy){
  3.     this.strategy=strategy;
  4.   };
  5.   this.run=function(){
  6.     strategy.run(this);
  7.   };
  8.    
  9. };
  10. client = new Client();
  11. client.name = "John";


  12. strategy = {
  13.    run: function (client) {
  14.         console.log(client.name);
  15.     }
  16. };

  17. client.setStrategy(strategy);
  18. client.run();


  19. strategy = {
  20.    run: function (client) {
  21.         console.log("name"+client.name);
  22.     }
  23. };

  24. client.setStrategy(strategy);
  25. client.run();

  26. strategy = {
  27.    run: function (client) {
  28.         console.log("first name"+client.name);
  29.     }
  30. };
  31. client.setStrategy(strategy);
  32. client.run();
复制代码
jsbin

在做一个strategy的demo, 这两个模式感觉很像。


感觉visitor,只是重点修改内部,希望过程不要一样。
而strategy,修改内部,过感觉上strategy更多是用于结果相同,过程不同


作者: woodheadz    时间: 22-12-2013 22:52
本帖最后由 woodheadz 于 23-12-2013 00:00 编辑
caoglish 发表于 22-12-2013 01:40
jsbin

在做一个strategy的demo, 这两个模式感觉很像。


挺好的例子。
不过我觉得在类似js这类支持匿名函数语言中,如果visitor接口简单到只有一个方法,不妨直接用匿名函数更简洁些。一点小意见
visitor模式还是比较常见,我现在做的项目里颇有些树的遍历算法,都是用visitor实现重用的。




欢迎光临 FreeOZ论坛 (https://hioz.im/ibbs/) Powered by Discuz! X3.2