找回密码
 FreeOZ用户注册
查看: 13702|回复: 60
打印 上一主题 下一主题

以Visitor设计模式应对需求变化

[复制链接]
跳转到指定楼层
1#
发表于 30-4-2009 10:45:22 | 显示全部楼层 回帖奖励 |倒序浏览 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有帐号?FreeOZ用户注册

x
网络上大多数的设计模式文章,都是采用了一些抽象的例子,今天我以实际的工作经验来描述一下在客户需求变化下如何通过良好的模式来应对。

假设我们已经有一个成熟的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 编辑 ]

评分

参与人数 3威望 +70 收起 理由
shenlh + 20 hoopoos,你好! 学习了你的visitor模式, ...
procoder + 20 谢谢分享!
coredump + 30 谢谢分享!

查看全部评分

回复  

使用道具 举报

2#
 楼主| 发表于 30-4-2009 14:09:57 | 显示全部楼层

回复 #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模块,假设你对拆手机装手机没把握的话,是不是后面的方案更好呢?
回复  

使用道具 举报

3#
 楼主| 发表于 30-4-2009 14:21:47 | 显示全部楼层
原帖由 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么?这本来就是手机的基本功能!
回复  

使用道具 举报

4#
 楼主| 发表于 30-4-2009 14:34:02 | 显示全部楼层
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给你和你的团队带来的收益。
回复  

使用道具 举报

5#
 楼主| 发表于 30-4-2009 14:50:39 | 显示全部楼层
原帖由 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? 因为实践出真知。
回复  

使用道具 举报

6#
 楼主| 发表于 30-4-2009 15:02:17 | 显示全部楼层
原帖由 black_zerg 于 30-4-2009 14:48 发表
不明白为什么要重编译5000个类。你的新逻辑全放一个class里也没问题,只不过在切入的时候,直接修改原先的代码,切入这个新类。这就是基于一个change request.需要做的事情.

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


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

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

总的来说,模式合理的运用,是基于对场合的正确理解和分析,不分场合用模式,然后把自己陷于一个两难的境地,是不可取的,如果因为这个原因低估模式的价值,那更不取。
回复  

使用道具 举报

7#
 楼主| 发表于 30-4-2009 15:05:27 | 显示全部楼层
原帖由 coredump 于 30-4-2009 14:57 发表


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


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

使用道具 举报

8#
 楼主| 发表于 30-4-2009 15:28:40 | 显示全部楼层
“假设我们已经有一个成熟的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类,飞的逻辑也分出去了,叫的逻辑也分出去了

这个讨论很好,大家都理解怎么回事的,不过个人的工作领域不一样,不一定能产生共鸣的。
回复  

使用道具 举报

9#
 楼主| 发表于 30-4-2009 15:46:27 | 显示全部楼层
原帖由 coredump 于 30-4-2009 15:35 发表


这个极至化很傻



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

使用道具 举报

10#
 楼主| 发表于 30-4-2009 16:30:11 | 显示全部楼层
原帖由 coredump 于 30-4-2009 16:14 发表
你这样的观点完全的是钻牛角尖了,别忘了还有重构这一说


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

假设一个项目经历这样的生命周期,类只增加不修改,那么,质量是可控制的,如果在某个节点,有若干个BUG,至少可以大部分认定,这些是最近新增加的class造成的,原先的是可靠的。100%的按照这个原则去做,我也同意你的说法,很傻很天真,为何我就钻牛角尖了?
回复  

使用道具 举报

11#
 楼主| 发表于 30-4-2009 16:33:40 | 显示全部楼层
原帖由 yuba 于 30-4-2009 15:58 发表
一直以为主人只是不在家

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

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


你这个回复完全牛头不对马嘴,一个谈技术的帖子,也能有这样的回复,真是可笑。如果你是想嘲笑我,那我告诉你,你没资格。
回复  

使用道具 举报

12#
 楼主| 发表于 30-4-2009 16:42:45 | 显示全部楼层
原帖由 coredump 于 30-4-2009 16:33 发表
按照你这个说法,我啥OO也不要,就要个VCS就行了,某个BUG肯定是某个changeset导致的,代码随便写

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


你还没理解我的意思,并不是说要查哪个BUG是什么造成的,而是因为模块化的需要,质量的需要,我请问:你的手机要加GPS功能,你是愿意拆开加芯片改电路好还是买个蓝牙模块好?你可以选择前一个方案,但是容易把手机弄坏。
回复  

使用道具 举报

13#
 楼主| 发表于 30-4-2009 16:51:53 | 显示全部楼层

回复 #37 coredump 的帖子

很好, 到了下班的时间了。

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

至于个人情绪么,只要是就技术论技术我总是欢迎讨论的,可惜总有些人文不对题,旁敲侧击。
回复  

使用道具 举报

14#
 楼主| 发表于 30-4-2009 19:31:43 | 显示全部楼层

回复 #40 yuba 的帖子

抱歉,是我没看懂,因为我从你的回复里没看出你看懂我的解释,有点绕口。

可能我的表达能力有问题,或者我太多假设了,向您道歉,多多包涵。

评分

参与人数 1威望 +30 收起 理由
coredump + 30 谢谢分享!

查看全部评分

回复  

使用道具 举报

15#
 楼主| 发表于 30-4-2009 20:40:54 | 显示全部楼层
原帖由 yuba 于 30-4-2009 19:42 发表


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

这是谁嘲笑谁啊?!


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

总之,可能我的表达能力有限,或者理解能力也有限,交流有问题。
回复  

使用道具 举报

16#
 楼主| 发表于 1-5-2009 09:24:44 | 显示全部楼层
“作为成熟的软件从业人员,大家都有自己的侧重和坚持而已”

非常赞同!
回复  

使用道具 举报

17#
 楼主| 发表于 7-5-2009 09:21:01 | 显示全部楼层
谢谢大家热烈的参与讨论,再友情提示一下,模式要活学活用,注意场合。我这里提Visitor模式不是说一有需求变化就用Visitor,是在特定的场合,已经完成了一个稳定的模块,冻结,打包,发布,这样的前提下,为了降低改动带来的风险,保持代码的integrity,采用的一种灵活策略。如果处在开发生命周期的当中,代码不稳定,频繁的需要修改,肯定是不能用Visitor的。
回复  

使用道具 举报

您需要登录后才可以回帖 登录 | FreeOZ用户注册

本版积分规则

小黑屋|手机版|Archiver|FreeOZ论坛

GMT+10, 4-5-2024 04:16 , Processed in 0.039220 second(s), 34 queries , Gzip On, Redis On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表