`
RednaxelaFX
  • 浏览: 3047794 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

对Strategy与Template Method模式的区别的一些讨论

阅读更多

准备抛弃自己原本在用的另一个blog了,开始把原本的一些文转过来。

这篇是[misc] 前两天与axx大聊天的一些记录

 

转载开始:

10/02 2007, 星期二

 

主要是对Strategy与Template Method模式的区别的一些讨论吧.


上个星期的后半段开始,翻出了一本叫《深入浅出设计模式(C#/Java版)》(莫勇腾编著,清华大学出版社出版)的书来读.每次都得解释一下,这本不是Head First Design Patterns.这本书大体上还不错,很实用,没什么废话,而例子又不会太生涩.如果以Head First Design Patterns来入门,那么之后读这本书对加强设计模式的印象挺有好处的.要说有什么不满,那就是我翻了半天没找到源码下载的地方,后来才发觉原来下载地址写在封面和前言里了...郁闷=_=

 

这本书是2006年8月出版的.不过里面用到的C#/Java的语言特性却没跟出版时间同步.例如说,在第3章,第89页,3.6.5 Double Check Locking (双检锁)这一节里,作者特别提到"Double Checking根本不可能在现有的Java编译器中运行成功,原因是JVM不支持。"这句话本身就有问题,前后分句因果关系错误:编译器只负责编译而不负责运行Java程序;double locking能通过编译.抛开句子本身的错误,它所陈述的内容也是有误导性的: 在Java SE 5或以上的版本,Java的内存模型已经被修正,因此double locking在"现有的Java"中是有效的.考虑到Java SE 5发布于2004年9月,Java SE 6发布于2006年12月,这本书却用了4页"详细"阐述了过时的观点,这就有误导性了.

 

当然,这本书主要涉及的不是C#与Java的语言特性,而是设计模式.它先讲了GRASP(General, Responsibility, Assignment, Software, Patterns)原则,然后把GoF的23个设计模式都举例说明了一次.大体来说是不错的,但...yeah, there always the "but".一些无法忽视的小节没整理干净,于是有"无意中"的错误.例如说,第3章,第46页,3.3.1一节中,图3-13的左下角,Client依赖于AbstractProductB的箭头指到了ProductB1上.不经意中就把读者给忽悠了 -_,

 

本书里的例子已经算生动的,不过有些地方要是能更接近现实更好.还是拿Singleton来说,教课书上经常会把它形容为"最简单的设计模式".没错,实现起来是很简单(?),但真要用起来总有些困扰的地方.最典型的问题或许是"粒度"问题--到底在什么范围内,这个类才应该是单件的呢? 假如单件的粒度太大,那么多件(Multiton)又该如何实现呢? 另外,什么样的状况算是对单件模式的滥用呢? 这些问题这本书都没能回答,比较可惜.而且给出的Java例子实现得相当糟糕,明明是讲Singleton,却写了一个错误的initialize方法,让使用者可以多次实例化Logger.


前面跑题跑了好远...回到主题上.我读那本书,快速过了一遍前面对每个GoF模式的单独讲解,然后看了一下第6章,模式的综合应用,想仔细看看作者是如何把各种模式组合用到实际例子中.结果第一个例子就出现了些让我摸不着头脑的地方.糟糕的是,本书所提供的源码里没有包括这个实例,书中文字没能说清楚的地方也无法通过代码来补完,诶.

 

这是一个Java实例,主题是一个扩展的日志记录器.背景是在讲解Singleton模式时举的Java例子,一个Logger的基础上实现扩展,能使用不同的格式将信息格式化,还要允许选择log的保存目标,包括文件,数据库等.格式化信息与保存信息都属于日志记录流程,但两者能独立改变.

 

让我们来琢磨一下作者的意图: 作者明确的提到了"调用应该很简单,并且在系统中只有单一的实例"--Singleton模式;之后又提到了"但是,有时我们又想让这个日志记录器的信息复杂点,最起码包含日志记录的日期和时间,或者可以看到线程ID..." 那作者在这个综合应用实例中是否还要继续使用Singleton呢? 带着这个疑问,我们继续看下去.

 

第一步,作者提出使用Strategy模式来封装各个保存信息的算法,如下:

第二步,作者转向格式化信息的问题,先提出可以再次采用Strategy模式:

作者随即否认这个组合的合理性,认为"这种应用方法是不合理的,如果我们应用模板方法模式,可以减小类的耦合(因为我们有责任设计低耦合的系统),因此可以将上面的类设计变为图6-3所示." 图6-3类似于下图:

在第一步,创建Recorder很合理,没什么疑问.但第二步作者所做的选择就让我摸不着头脑了.作者提出的理由只有上面引用的那一句,没任何其它解释,也没提供这个例子的源代码.他是凭什么认为Strategy模式比Template Method模式的耦合度高呢?

 

要解决这个疑问,我们得回头看看Strategy和Template Method模式的最常见结构:


顺带把State模式的结构也放在这里:

很明显,Strategy模式与State模式的结构,在UML图上看起来是一样的.代码编写的方法也是一样的.但很少人会把这两种模式混淆,因为它们背后有具体的意义,应用场景不一样.而Strategy与Template Method模式的关系就比较微妙了,因为它们的应用场景时有重合.乍一看它们的结构差别很大,但仔细观察会发现,这两种模式的结构在局部是同构的.请看Strategy模式的那张图:如果将Strategy接口合并到Context类中,使AlgorithmInterface()方法成为Context的一个成员方法,则原本Strategy接口的两个实现类,就会顺而变为Context的子类.如是,这个结构就变成与Template Method模式的结构一样的了.不太准确的说,Strategy模式可以看成是将Template Method模式所要封装的变化的一部分提取出来的结果,因而在实现时有同构的部分.那这两种模式到底区别在哪里? 从表像上看,Strategy模式的类层次结构可能会比Template Method模式的浅;Strategy模式潜在的鼓励使用者直接实现接口来实现扩展,而Template Method模式只能让使用者通过继承来实现扩展.不过实际上最影响选择的因素并不在类层次的深浅上.

 

根据《深入浅出设计模式(C#/Java版)》所述,


Strategy模式的应用场景是:
1. 多个类的分别只是在于行为不同
2. 你需要对行为的算法做很多变动
3. 客户不知道算法要使用的数据

 

Template Method模式的应用场景是:
1. 你想将相同的算法放在一个类中,将算法变化的部分放在子类中实现
2. 子类公共的算法应该放在一个公共的类中,避免代码重复

 

仔细体会作者所提出的这几个应用场景,你会发现它们其实没什么区别,用Strategy或Template Method模式都能完成要求.换句话说,作者没抓住这两个模式区别的"痛处"来给予详细讲解.下面我们再换个出发点来看看这两种设计模式.

 

我们知道,设计模式中有这么一个原则: Prefer composition to inheritance.这句话的背景是OO初期大家都把继承看作是万能的,并过度使用继承来实现多态->可扩展.理解原则的时候不能脱离它的背景,不然就成盲从了.Template Method模式应该是伴随着OO的出现而萌生的.它是OO中最直观的思考方式的结果.基类留下可变化的空间给子类,由继承类来决定具体行为.听起来是不错,不过...一旦基类的接口发生了变化,每个继承类都得跟着修改才能够继续使用.这就是所谓高耦合与难维护的说法的来源.

 

Strategy与Template Method模式算是composition与inheritance的典型应用了,如果它们真的在功能上能完全互换,那何必要后者呢,全部都用前者不是很好么? 再怎么说,一个倾向于加深类层次结构的设计通常会使设计变得复杂,令后期维护变得困难.

When deciding between inheritance and composition, ask if you need to upcast to the base type. If not, prefer composition (member objects) to inheritance. This can eliminate the perceived need for multiple base types.

Which should I prefer: composition or private inheritance? Use composition when you can, private inheritance when you have to.

有一个说法总结得不错: 到底该倾向于composition还是inheritance,决定于"变化的是什么".如果基类的接口变化得很频繁,那么使用inheritance绝对是个噩梦;如果只是给基类新增方法,那么坚持使用composition的话就得新增很多个delegate.

这么说来,Strategy与Template Method模式之间的区别,也是在"变化的是什么"这个问题上了.

 

注意到,Strategy模式中,为了让Context类能够调用,Strategy接口里声明的方法一般是公有的.Template Method模式则不然,基类中留下的虚方法并不一定要是公有的,只要保证对继承类可见就行.也就是说,Template Method模式允许编写库的人采取更紧的访问限制,而Strategy模式则很难做到相同等级的限制.假如使用者获得了一个Strategy接口的实现类的实例,他并不一定要将这个实例放入"原本应有"的那个Context,而可以随意使用其中的接口方法.Template Method模式可以利用protected的访问权限,牺牲一点面向对象的封装性,给自己的继承类一定的访问特权,来把一些访问限制在"体系内",从而限制了外部对内的访问.这仍然只是表象,不过我们已经接近问题的本质了.

 

这带来的区别是什么呢? Strategy模式允许外界使用其接口方法,因而可以将这个接口方法认为是"一整个算法";而Template Method模式可以限制所留下的虚方法只对其继承类可见,外部使用者不一定能够直接使用这些虚方法,因而可以将这些虚方法认为是"一个算法的一部分".GoF的设计模式那本书里有这么一句话:"Template methods use inheritance to vary part of an algorithm. Strategies use delegation to vary the entire algorithm.",说的正是这个问题.回到具体问题上,如果我们要封装的算法适合于提供给用户任意使用,是"一整个算法",那么用Strategy模式较好;如果要封装的变化是一个算法中的部分(换言之,大算法的步骤是固定的),而且我们不希望用户直接使用这些方法,那么应该使用Template Method模式.就此,问题的"痛处"算是抓住了.

 

回到书中的例子.为什么使用Template Method模式比较好呢? 我觉得是因为那个format()方法并不应该被用户直接单独调用,因而用protected限制住了对它的访问.这就不适合Strategy模式了(用Strategy意味着默认用户单独去使用算法).

但是话说回来,作者提到了Singleton.要是用了Template Method,这Singleton基本上就泡汤了.但是又没有源代码看看作者到底实现出来的是什么样的,无法猜透他的想法啊.

 

为了这么一个问题,我硬是跟axx大争了一个晚上...我总是觉得Strategy跟Template Method模式在使用中没什么区别,而根据Prefer composition to inheritance原则,Strategy模式相对更合适于较多的场景.axx大则不停重复两种模式带来的访问限制不一样,但一直没能表述得让我明白.幸好axx大脾气好,不然这么争一次还真伤元气 XD


后来又讨论到了"恶心的代码"的问题.这是我很感兴趣的方面,因为我觉得这是工作中程序员无法回避的问题.我还是一个没参加过公司里实际开发的学生,并不了解外面的真实状况.所以axx大肯说些这方面的事的时候,我就特别想听.


当一段代码并不是自己新建,而是在别人写好的,已有的基础上修改/扩展时,这问题就来了.特别是当公司并不特别严格要求"软件过程",但又把工期定得十分紧迫时,问题就特别明显.axx大提到了一个现象,我猜应该相当普遍吧: 架构没仔细设计好,然而工期又紧,写代码的程序员就只好硬着头皮上了;碰到架构有问题的地方就hardcode一些hack去解决,逐渐这些hardcode积累多了,后来的程序员也都不想去改,没人想去重构也没那时间,最后,整个code base就像垃圾堆一般,不得不砍掉重炼.

 

几个经典的东西: "超大规模switch""无敌的int""百变的struct".诶你看,有时候这"笨手笨脚"的Java就是好,你就是没办法把一个int当指针用,所以C++里的不少"恶心代码"都写不了. = = (Java有Java的恶心代码...)

 

最近不知道为什么突然对软件工程的方法好感倍增.可能是因为苦头吃多了吧,也可能是因为好久没做什么认真的开发,前面快一年的时间都钻在逆向工程里吧.呵呵.


另外也和axx大讨论到些语言问题.恩,因为前段时间在攻C#,稍微带起了一点钻研语言的魂...这个时候真不适合做这种事情啊.Anyway,就在这两天稍微看了下D语言相关,用Poseidon IDE搭配DMD 2.004写了些测试.果然是有趣的东西,以后要是有时间再把初步接触的感觉记下来吧~


 

FX 10/02 2007, 05:16 Say:

 

上面有两张图有点小错...例子里Step 2的Template Method版本里,Logger.format()应该是abstract的.后面Template Sample里,AbstractClass的primitive operation都应该是protected的.
-
不过懒得改了...就这么凑合吧.昨晚画完UML图没保存,直接导出成JPEG就把编辑器关了.呜...||

分享到:
评论
1 楼 lwwin 2008-04-08  
都转过来好了,其实偶对设计模式完全没有心得,就凭感觉在写
万能的STRUCT和INT是常见的东西了……
对外都是VOID*了么……

相关推荐

    Java24种设计模式,Java24种设计模式,24种设计模式,学会了这24种设计模式,可以打遍天下无敌手,设计模式非常重要

    9、模板方法模式TEMPLATE METHOD PATTERN 10、建造者模式BUILDER PATTERN 11、桥梁模式BRIDGE PATTERN 12、命令模式COMMAND PATTERN 13、装饰模式DECORATOR PATTERN 14、迭代器模式ITERATOR PATTERN 15、组合...

    java设计模式教程+源代码

    AbstractFactory ( 抽象工厂 ) FactoryMethod ( 工厂方法 ) Singleton ( 单态模式 ) Builder ( 建造者模式 ) Prototype ( 原型模式 ) ...TemplateMethod ( 模板方法 ) Visitor ( 访问者模式 )

    C#版 24种设计模式

    工厂方法模式(Factory Method Pattern) 观察者模式(Observer Pattern) 建造者模式(Builder Pattern) 解释器模式(Interpreter Pattern) 命令模式(Command Pattern) 模板方法模式(Template Method Pattern) 桥接模式...

    C++设计模式(Design Pattern)范例源代码

    23种设计模式(Design Pattern)的C++实现范例,包括下面列出的各种模式,代码包含较详细注释。另外附上“设计模式迷你手册.chm” 供参考。 注:项目在 VS2008 下...模板方法模式(Template Method) 访问者模式(Visitor)

    C#设计模式_设计模式_C#_

    模板方法(Template Method) 14. 命令模式(Command Pattern) 15. 迭代器模式(Iterator Pattern) 16. 观察者模式(Observer Pattern) 17. 解释器模式(Interpreter Pattern) 18. 中介者模式(Mediator Pattern) 19. ...

    01-制造工具的工厂模式(1).html

    模板方法模式( Template Method ) 观察者模式( Observer ) 迭代子模式( Iterator ) 责任链模式( Chain of Responsibility ) 命令模式( Command ) 备忘录模式( Memento ) 状态模式( State ) 访问者模式...

    09-通过容器实现的外观模式(2).html

    模板方法模式( Template Method ) 观察者模式( Observer ) 迭代子模式( Iterator ) 责任链模式( Chain of Responsibility ) 命令模式( Command ) 备忘录模式( Memento ) 状态模式( State ) 访问者模式...

    00-初探 Laravel 和其中的设计模式(3).html

    模板方法模式( Template Method ) 观察者模式( Observer ) 迭代子模式( Iterator ) 责任链模式( Chain of Responsibility ) 命令模式( Command ) 备忘录模式( Memento ) 状态模式( State ) 访问者模式...

    12-附录 1 设计模式的七大原则(1).html

    模板方法模式( Template Method ) 观察者模式( Observer ) 迭代子模式( Iterator ) 责任链模式( Chain of Responsibility ) 命令模式( Command ) 备忘录模式( Memento ) 状态模式( State ) 访问者模式...

    Delphi模式编程第一分卷

    第25章 模板方法模式(Template Method) 25.1 模式解说 25.2 结构与用法 25.2.1 模式结构 25.2.2 代码模板 25.2.3 问题讨论 25.3 范例与实践 25.3.1 模板方法在离线数据库系统中的应用 25.3.2 范例小结 第...

    Delphi模式编程第二分卷

    第25章 模板方法模式(Template Method) 25.1 模式解说 25.2 结构与用法 25.2.1 模式结构 25.2.2 代码模板 25.2.3 问题讨论 25.3 范例与实践 25.3.1 模板方法在离线数据库系统中的应用 25.3.2 范例...

    java版本二十三种设计模式.zip

    - 模板方法模式(Template Method) - 策略模式(Strategy) - 责任链模式(Chain of Responsibility) - 中介者模式(Mediator) - 访问者模式(Visitor) - 命令模式(Command) - 解释器模式(Interpreter) - 迭代器...

    常见的十种设计模式Java工程文件

    2. **Template Method模式**:Template Method模式定义了一个操作中的算法骨架,而将一些步骤延迟到子类中。它使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 3. **Command模式**:Command模式...

    java与模式光盘源码

    行为型模式如观察者(Observer)、模板方法(Template Method)和策略(Strategy)等,关注对象之间的交互和责任分配。 在Java与模式的光盘源码中,你可以找到这些模式的实际应用示例,这对于学习和掌握设计模式至...

    设计模式PPT

    创建型模式用来处理对象的创建过程,主要包含以下5种设计模式:  工厂方法模式(Factory Method Pattern)  抽象工厂模式... 模板方法模式(Template Method Pattern)  访问者模式(Visitor Pattern)

    C++设计模式.pdf

    Template Method模式定义了一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 #### Strategy模式 Strategy模式定义了一系列的算法,并将每一个...

    python实现常用的23种设计模式:含源代码、详细文档说明

    1、模板方法模式【Template Method】 2、观察者模式【Observer】 3、状态模式【State】 4、策略模式【Strategy】 5、职责链模式【Chain of Responsibility】 6、命令模式【Command】 7、访问者模式【Visitor】 8、...

    Java与模式

    包括责任链模式(Chain of Responsibility)、命令模式(Command)、解释器模式(Interpreter)、迭代器模式(Iterator)、中介者模式(...Strategy)、模板方法模式(Template Method)和访问者模式(Visitor)...

    24种设计模式以及混合设计模式

    如策略模式(Strategy)、模板方法模式(Template Method)、观察者模式(Observer)、命令模式(Command)、迭代器模式(Iterator)、访问者模式(Visitor)、备忘录模式(Memento)、状态模式(State)、职责链...

Global site tag (gtag.js) - Google Analytics