GOF设计模式学习笔记
--辨析各种模式的要点和相似模式之间的区别
作者:agipenia
1 正文之前
1.1 要阅读本文,需要:
l 有至少1年(最好是3年)以上使用C#、Java等面向对象语言开发经验。
您没看错,不是招聘,但是就是需要面向对象开发经验,没有面向对象开发经验的不要看。
当然,也有些厉害的,做过一个月开发就可以学习设计模式了。
l 阅读完了GOF设计模式至少到第4章的一半,而且脑海中有疑问。读过之后脑海没疑问的,阅读本文无益。
l 必须先体会:什么是高内聚,低耦合原则。(这个,其实也是阅读GOF原文的前提)
以及:为什么要高内聚,低耦合。
提示:把职责分清楚。
n 高内聚:紧密相关的功能、信息等放在一起;不相关的功能、信息分开管理。
n 低耦合:互相协作的单元,交互时接口要简洁。彼此内部的变化,尽量不要影响到对方。
n 还是自己去百度:“高内聚低耦合”的帖子看,有感觉了再回来。。。。(其实我喜欢Google)
l 您曾觉得自己写的一堆代码臭不可闻,并且认认真真的对它们进行了重构,虽然最后还是干脆全部扔到重做了。(这一条也是读懂GOF原文的重要条件)
l 不符合以上条件的,请不要阅读此文。欧阳锋曾经曰过:“巴巴西勒普 ,巴巴西勒普,巴巴西勒普!”本文没有九阴真经那么牛叉,但是有那么难懂。。。
l 本文要对照着GOF原文阅读。本文只是笔记,对原文的依赖度非常高。
1.2 关于学习设计模式的建议
l 不要动不动就想用这个模式,那个模式。
设计模式只是手段。可维护性高,可读性高,可扩展性高、优雅的代码才是目的。很多时候(应该是大多数时候),不需要设计模式就可以搞定的。
1.3 怎样使用设计模式解决设计问题
l GOF1.6节目录:
n 寻找合适的对象
n 决定对象的粒度
n 指定对象接口
n 描述对象的实现
n 合理运用复用机制
n 迎接变化
以上几条,其实应该是评价”某个情况下是否应该使用某个设计模式”的参考原则。但这不是全部的原则,没有最终的评价,需要根据实际情况,自己去决定是否合适。
l 很多人都认为,设计模式还有个好处:方便沟通。
n 我很赞同这句话,如果大家都理解设计模式,那就可以在交流时,用一个名词代表自己脑海中的一组对象以及他们的交互方式,大大提供交流的效率。
n 但是“交流时只需提到一个设计模式的名字即可”,这个要求其实很高。非常高。几乎不可能。。。因为双方都要深刻理解同样一族设计模式。
n 不过那也没关系。即使一个名字代表不了全部,也比交流时还要去完整描述那一大片细节要方便。
n “使用设计模式来提高沟通效率”,从我多年的实践经验看,这往往只是一个美好的愿景。实际情况往往是:对于大多数用得不太频繁的设计模式,如果仅仅用一个名字来沟通,只能加深彼此的莫名其妙,更有小气的人甚至会认为对方是在纸上谈兵地显摆。
造成这种状况的结果有很多,例如很多人本就不太关注,或者各种设计模式本身就不是那么简洁易懂。另外请注意到,这是在软件开发的过程中的沟通。 软件开发是那么复杂的一种工程行为,它的复杂性,大多数不是来自于要解决的问题本身,而是来自于人。
n 这个问题的结论是:我们可以期望,身边的人都深刻理解设计模式,藉此可以加强沟通效率。但生活中往往没有我们想象的那么美好,如果你说到一个模式的名字对方听不懂或者相反,那才是正常现象,请不要太在意。
l 一个通用短语,代替脑海中的巨量信息?
n GOF设计模式里面,有很多模式具有成为一个名词短语的潜力(后面的章节中,我将壹壹给出我的看法) 。对于那些你觉得有潜力的模式,可以在设计、开发的过程中尝试直接以设计模式的名字跟对方沟通,然后看看对方的反应。
n 哪几个设计模式具有这样的潜力,取决于:
u 它的名字是否取得好?名不正则言不顺。
u 它是否特点鲜明,如果某个设计模式总是被人误认为是另外一个模式甚至是设计模式以外的某个概念,那么最好不要在沟通时把这个设计模式短语化。否则会给本来就很困难的沟通添加新的复杂性。
u 它是否被经常用到?
2 创建型模式
2.1 Abstract Factory 抽象工厂
l 意图(原文):提供一个“创建一系列相关或相互依赖对象”的接口。
l 仅图中红色部分是抽象工厂模式的要点。包括两个方面:
n 用工厂方法提供对象。
n 提供一组对象。Client得到的这一组对象是可以互相协作的。(Client不会同时得到PMWindow+MotifScroolBar这样一组不能协作的对象。)
l 目的是:使得蓝色部分的创建过程以及它们的具体类型对于Client不可见。
l 这个模式,易于成为通用短语。
2.2 Builder 生成器
l 意图(原文):将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。
l 红色部分,是生成器模式的要点。包括两个方面(上下两组图,上面是原理,下面是例子) :
n Builder负责创建“一个复杂对象的各个部件”。
n Director负责将这些部件拼装起来。
l 从Client角度看,抽象工厂和生成器最大的区别是:
n 前者产生一组对象,抽象工厂保证这一组对象是互相可以协作的。
n 后者只产生一个对象,生成器将这个对象的内部拼装好了。
l 这个模式,易于成为通用短语。
2.3 Factory Method 工厂方法
l 意图(原文):定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method使一个类的实例化延迟到子类。
l 前面两个模式的示例图里面,有几个绿色线框…
n 那些线框里面都是工厂方法。
n 抽象工厂和生成器,经常(但不一定)会用工厂方法来声明和实现用于创建对象的接口。
l 这个模式,具有成为短语的潜力。
2.4 Prototype 原型
l 意图(原文):用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
l 效果(原文):Prototype有许多和Abstract Factory,Bulder一样的效果:它对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目。此外,这些模式使客户无需改变即可使用与特定应用相关的类。(我认为这段话其实与本模式关系不大,后详)
l 要点在哪里?(要点见下页红色)
n 如果某个工厂方法里面,是用“拷贝原型”的方式来产生新的对象实例,那么我们可以认为这是在实践一个原型模式。原型模式的要点,仅仅会在某些工厂方法的实现里面体现出来。
l 这个模式,易于成为通用短语。(但是我不喜欢去提到它,因为它太过于描述细节了。。。
2.4.1 原型模式的困惑
l Prototype模式,与Abstract Factory和Builder 有什么联系和区别?它自己最独特的地方在哪里?
这个问题,困惑了我很久。很长一段时间内,我一直无法找到Prototype与后面两者的本质区别。
n Prototype模式的“意图”这一段话,其实描述的是Prototype模式的实现方式。看了这段话之后,我只能明白“做什么” ,却不明白“为什么要这么做”。
n 从效果上看, “Prototype有许多和Abstract Factory,Bulder一样的效果”。这句话,实际上不利于描述Prototype的独特之处,反而加深了我的困惑。
n 我在网上找到一篇帖子,是一位名叫“苏飞”的大虾写的。(http://space.cnblogs.com/group/topic/13210/)
这篇博文里面的“调色板”的例子,貌似比GOF原文3.4的“动机”一节里面的“GraphicTool+多种音符”的例子,更能体现Prototype的最大的特点。
2.4.2 原型模式的要点
l Prototype的要点,在GOF原文中3.4的“适用性”这一段的第三条:“当一个类的实例只能有几个不同状态组合中的一种时,建立相应数目的原型并克隆它们,比每次使用合适的状态手工实例化该类更方便一些。”
l 换一种方式表达:
n 对一个类的实例可以分组,组与组之间的区别,不需要用不同的Class来描述,组内的区别更小。
n 对每一组建立原型,用拷贝的方式产生组内的新实例。
l 为什么这一段才是要点?
n 看这个例子:
考虑一个场景,我们需要使用”颜色类“的对象。
u 首先,我们一般不会为每一种颜色新建一个类。(那样是不是太勤奋了。。。。额。。。勤奋有时候有贬义吗?)
u 其次,我经常会要用到”纯红“,”纯蓝“,”纯黑“,”海蓝”,“淡蓝”等等等等这些颜色的对象,在需要这些对象的时候,我不想每次都去用特殊的一组RGB值去创建一个对象,最好是直接用颜色的名字就可以拿到相应的对象才好。
u 那么就用一个工厂方法来负责提供这些对象吧。这个方法里面可以用早已定义好的原型,拷贝出新的对象提供给client。
u 这就是原型模式的典型应用场景。
u 当然,这个例子里面,每一个颜色对象,还可以用“享元”的方式来提供。
n 关于Prototype的其他描述,大多数都可以合并到Abstract Factory,Builder,Factory Method里面去理解记忆。
例如,我们可以把“从原型拷贝生产新实例”这件事,封装在某一个方法里面。那么从该方法的调用者看来,这个方法其实是工厂方法的一种形式而已,并不属于Prototype模式的一个部分。这个方法的声明和实现,是工厂方法模式和原型模式这两个设计模式的组合。
l 请注意,我的理解和GOF原文的描述是不一样的,我觉得GOF不应该把Prototype和其他模式混到一起而掩盖了Prototype的特质。
我这样的做法就像是,某人说了一句话,然后我告诉他,“你其实不是这个意思…..”
所以,是否要参考我的意见,请各位自己斟酌。
2.5 Singleton 单例
l 额…这个词已经是软件开发领域的通用短语了。
3 结构型模式
3.1 Adapter 适配器
l 意图:将一个类的接口转换成客户希望的另外一个接口。A d a p t e r模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
l 红色部分,是适配器模式的要点:
n 为既存的客户代码和功能提供者进行接口适配,或者说接口的转换;(上图中的既存客户代码是DrawingEditor,既存功能提供者是TextView。)
n 适配器本身不提供新的功能。
n 一般情况下适配器也不会减少既存功能提供者的功能。
l 这个模式,应该已经成为通用短语了?你可以尝试一下。。。
3.2 Bridge 桥
l 意图:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
l 原文中,黑色线框部分,是桥模式的要点。
n 将抽象部分与它的实现部分分离,使它们都可以独立地变化。(原文”意图“一节,又拷贝一遍。)
l 对桥模式的要点,我的结论是,我不知道。我看不懂。或者一定要我说真话的话,我希望在GOF全书中去掉这个模式,那样我会觉得很快乐。。。。
l 跟我持完全相同观点的,或者认为我完全胡言乱语的,可以不要看下面的部分了。。。。
n 上图中Window和WindowImp的关系,是“抽象和实现”的关系吗?
u 不是的,从语法的角度来说,不是的。它们向外提供的方法的声明是有区别的。
u 那么从逻辑上能说得通吗?
l 两个类的职责不一样---它们做的是是不同层次的事情。
l WindowImp负责drawText和drawLine。Window类使用WindowImp提供的drawLine功能实现drawRect().另外转发drawText()消息。
l drawLine是drawRect的实现还勉强说得过去,但drawRect是drawLine的抽象,就怎么都说不通了。
l 实际上,还不如将drawLine是基本动作,drawRect是在基本动作上进行的复杂动作。drawRect和drawLine的关系,不是抽象和实现的关系,是“高级功能和基本功能”之间的关系。
n 上图中蓝色部分,是“抽象与实现”的关系吗?
u 是的。可是这样的做法,是一个习惯于面向对象设计编码的程序员一天到晚都在做的事。这就是用继承和多态的手段实现抽象与实现分离而已,是基本功。
n “抽象与实现分离”,这是模式这个级别的问题吗?不是的,这是设计原则这个级别的说法。但是桥模式的其他章节都描述得模糊不清,与这个“简单”的意图没有直接的严谨的关系。
n 而且我从头到尾看不到这个模式里面有什么概念或者行为跟“桥”这个名字有关系的。
n 有没有高人点拨我一下,桥模式的要点到底是什么?
l 这个模式,不易成为通用短语。
3.3 Composite 组合
l 意图:将对象组合成树形结构以表示“部分-整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
l 这个模式非常有特点,GOF原文给的篇幅非常大。个人认为该模式一般不会与其他模式混淆,不再多说了。请反复阅读GOF原文。
l 这个模式,易于成为通用短语。
3.4 Decorator 装饰器
l 意图:动态地给一个对象添加一些额外的职责。就增加功能来说, D e c o r a t o r模式相比生成子类更为灵活。
l 惯例。红色部分是要点。
n 装饰器通过继承的方式,提供“与被装饰的类(或者实例) 相同的”接口。
n 装饰器会将消息转发给一个承担了Component主要职责的实现类实例,由该实例完成主要职责功能。
n 装饰器本身不应该实现承担Component声明的主要职责。
n 装饰器提供主要职责之外的功能。(上图中的AddedBehavior)
l 为什么上图中,ConcreteDecoratorB本身不在“要点”的范围之内,而它的一个方法却在要点的范围之内?
n 因为Decorator模式其实并不一定要采用Decorator与ConcreteDecoratorB这样的继承关系。即“Decorator要定义为抽象类”,这一点对于Decorator模式不是必须的。
n GOF全书中,有多处这样的问题,描述的时候不够“本质”。。。我这么说,只是为了提醒大家,看这本书的时候,要注意这个问题。虽然说瑕不掩瑜,但是把无关的问题撇开,会理解的更清晰不是。
n 我对GOF是滔滔敬仰的。要不然记这么多笔记!
l 这个模式,易于成为通用短语。 --但是,对沟通双方的要求比较高。
3.4.1 Decorator的一个精彩的例子
理解这个例子,需要理解Spring的方面(切面)的概念和实现原理。否则请跳过这一节。
l 上图中:
n Client通过BeanFactory获得一个AbstractT类型的对象。该对象的实际类型,在配置文件中指定为T。
n 在BeanFactory.getBean()方法中,不仅产生了T类型的对象t,还为t产生了一个动态代理对象dpObj。并将dpObj提供给Client对象使用。
n Client对象调用dpObj.f();
n dpObj.f() 方法内部做了两件事:
u 转发消息给t对象。(调用t.f();这样的话,在Client看来,dpObj完成了AbstractT所承诺的职责。)
u 记录日志(这是超出Client意料之外的一个功能)。
n 这里的DynamicProxyObj类,名字里面有Proxy,但起到了装饰器的作用。关于装饰器与代理的关系和区别,后面还会详细说明。
l 在Spring框架中,我们经常通过容器(对象工厂)获得一个想要的对象实例。该实例的实际类型以及其他信息,我们通过配置文件而不是代码告诉容器。
l 在一个项目的编码阶段前中期,我们有时候还需要配置一个aspect(方面/切面),这个切面的功能是,“在所有的方法被调用时,记录Debug级别的日志”的。
l 如果我们定义了这样一个切面的话,在我们从容器中获取对象时,Spring框架会“偷偷地”为每一个对象提供一个装饰器,由该装饰器来进行切面定义的“记录日志”的功能。
3.5 Façade 外观
l 意图:为子系统中的一组接口提供一个一致的界面, F a c a d e模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
l 红色是要点。
n 当我们的代码里面出现了左边的状况,可以考虑一下重构成右边的样子。
n 但是,还是有一些细节需要注意的:
u Façade只是外观,只负责消息的转发,除此之外,不应该担负其他职责。不负责创建对象,不负责消息排队,不负责状态,不负责处理Exception,以及等等其他职责。
u Façade向外掩盖了子系统内部的分工。如果我们打电话给中国电信客服,客服小姐告诉我们,“要完成这件事,你得按顺序打以下3个电话:…”。我们一定会投诉不是。。(但是,好像迁户口就得自己去找,我找呀找呀找呀找。。。)
n 很多时候,我们要更多的从重构的角度去考虑一个模式的要点。即:在出现问题的时候再考虑模式的问题。关于OO设计原则,有一句很有名的话(对不起我不记得是谁先说的了):“运用这些原则的最好方式是有的放矢,而不是主动出击。 ”这句话对设计模式,一样有效。
l 这个模式,易于成为通用短语。 --但是,对沟通双方的要求比较高。
3.6 Flyweight 享元
l 意图:运用共享技术有效地支持大量细粒度的对象。
l 要点:
n 运用共享技术有效地支持大量细粒度的对象。(嗯,这个模式的“意图”就已经体现出本模式的要点。这让我觉得很快乐。)
n 但是, Flyweight还有一个要点,在图里面反映不出来。。。
u GOF原文的动机里面,有这样一段话:
f l y w e i g h t不能对它所运行的场景做出任何假设,这里的关键概念是内部状态和外部状态之间的区别。内部状态存储于f l y w e i g h t中,它包含了独立于f l y w e i g h t场景的信息,这些信息使得f l y w e i g h t可以被共享。而外部状态取决于F l y w e i g h t场景,并根据场景而变化,因此不可共享。用户对象负责在必要的时候将外部状态传递给F l y w e i g h t。
u 我觉得,Flyweight最好都做成“不包含外部状态”的,或者干脆说是“无状态”的。
l Flyeight模式在Jdk里面有一个非常好的实践:java.lang.String.
l 这个模式,易于成为通用短语。 --如果对方不明白,应该去跟他灌输这个模式。
3.7 Proxy 代理
l 意图:为其他对象提供一种代理以控制对这个对象的访问。
l Proxy模式在类结构图上看貌似很简单。但图上体现不出要点。要点是:
n 为被代理的对象提供访问控制。(其实代理对象还可以做很多其他事,这是最常用的)
n 有时可以分担一部分被代理对象的职责。
n 将那些“与某对象有关,但有时候它又不想关注”的事情分离出去,交给代理做。
l 代理很像一个秘书,可以帮访客安排时间或者拒绝访客;如果老板授权的话,还可以直接代替老板做一些事情,例如批一个星期的请假条什么的。
l 代理模式与其他模式的区别。
n 与装饰器比较:
u 装饰器不会关注被装饰的对象的状态;而代理一般情况下要关注被代理的对象的状态。
u 装饰器仅为被装饰的对象提供额外的功能;而代理与被代理的对象关系比较密切,要对被代理的对象负责。例如,装饰器不会拒绝一次访问,而代理在有的时候会这么做。
n 与组合模式比较:(其实这两个模式相似度比较低了,大概提一下)
u 组合模式一般负有“容器”的职责,可以同时聚合(容纳)多个组合模式的对象。
u 代理一般只会聚合一个被代理的对象,不会同时担当多个对象的代理。
n 与Adpater比较:
u Adapter会一个对象提供新的接口,Proxy不会。
u 跟装饰器一样,Adapter在传递消息时,不关注目标对象的状态,而代理往往需要关注。
l 这个模式,易于成为通用短语。 --但是,我建议不要仅仅使用这个短语来表达你的想法。对于严谨的设计描述来说,这个模式可以适用的范围稍微广泛了一点。太灵活了。
4 行为型模式
l CHAIN OF RESPONSIBILITY(职责链)-不进行详细讨论。
n 意图:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。(此模式特点鲜明,我就不添足了)
n 这个模式,易于成为通用短语。
l INTERPRETER(解释器)-不进行详细讨论。
n 意图:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。 (此模式特点鲜明,应用的范围比较窄。 需要使用这个模式的人,请自行百度相关的高级议题。)
n 这个模式,不易于成为通用短语。
l ITERATOR(迭代器) -不进行详细讨论。
n 意图:提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。(同样是一个特点鲜明的模式,使用的频率非常高而且好像也不太容易被滥用。经常写代码的人自然会熟悉它。)
n 这个模式,已经是流行语了。
4.1 Command 命令
l 意图:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
l …红框框呢?我觉得这个模式的要点用图其实不好表达。
l 那么,Command模式的要点是什么?
n 将我们平时定义为一个类的方法的那个东东对象化。
l 这样有什么好处?
n 例如:我们可以自己把方法保存起来,等会再调用。
n 例如:我们可以取到这个对象的名字,然后记录日志。(当然,这一点用Java或者DotNet框架提供的反射功能也可以做到)
n 例如:我们可以要求一个方法对象在提供一个execute()方法的同时,还提供一个rollback()方法,使得我们可以让一个操作滚回去。
n 总之,把一个方法(动作/命令/操作)对象化了之后,我们可以对这些方法定义一些我们需要的管理方面的接口,并通过这些接口对方法本身进行管理。
l 这个模式,易于成为通用短语。
4.2 Mediator中介者
l 意图:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
l 先分析下原文的“适用性”一节:
适用性-在下列情况下使用中介者模式:
n 一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。--这一点我是同意的。
n 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
u 这一点我反对,或者说,原文描述得不够准确。如果我们编写的是一个应用程序,不应该去追求所有的东西都有很高的复用性。应该把不经常变的和易变的部分分开。而Mediator模式恰好可以做到这一点:相比之下,上面的ColleagueX比较稳定,而Mediator封装了易变性。我们用这种方式,把可变现限定在Mediator里面了。
u 像上图中的例子,我们可以想象ABCD是四个UI控件,把它们组合好就可以形成一个UI画面。但是,UI界面往往是很复杂的,我们用不同的Mediator来组织这些控件,就可以形成不同的界面。这样,就把每一个界面的复杂的UI逻辑封装在了一个Mediator里面,而编写ABCD这几个控件的时候,就可以把注意力放在控件本身的表现力上面。
n 想定制一个分布在多个类中的行为,而又不想生成太多的子类。--这一点我不太明白,有没有人讨论下。。。
l 本模式的要点是什么?(本模式的要点,要从重构的角度去看)
n 让多对多的依赖关系,变成多对一(或者一对多)的依赖关系。(注意,不要演变成“多对一 + 一对多”的结果,那样效果不好。)
l 这个模式,不易成为通用短语。
4.2.1 中介者模式注意事项
l 需要注意的是:Mediator很容易演变成一个上帝类。那么怎么避免呢?
原则上讲,还是依据高内聚低耦合的原则。这里提几个细节:
n 用合适的模式来实现Mediator,例如用Observer模式。
要让“多对多变成多对一”,是需要技巧的。如果没有处理好,结果很容易变成”多对一 + 一对多“。也就是说,各个ColleaguieX对象依赖Mediator,同时Mediator依赖了ColleaguieX对象。如果出现这种状况,有可能因为是Mediator设计得不够好(也有可能模式选择得不合适),应该想办法只留下”多对一”的依赖关系。
n Mediator的主要职责,是协调ColleagueX这几个对象的通信,所以不要把“创建ColleagueX对象”的职责放到Mediator对象里面。
l Mediator与Façade的区别
n (原文摘要)Facade ( 4 . 5 )与中介者的不同之处在于它是对一个对象子系统进行抽象,从而提供了一个更为方便的接口。它的协议是单向的,即Facade对象对这个子系统类提出请求,但反之则不行。相反, Mediator提供了各Colleague对象不支持或不能支持的协作行为,而且协议是多向的。
n 换一句话说:Façade为一组内聚的对象,提供一个统一的外部接口,Mediator为在一组互相协作的对象内部起协调作用。
4.3 MEMENTO 备忘录
l 意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
l 要点:
n 这个模式的特点比较鲜明,也没有容易混淆的其他相似模式,不进行深入讨论。
l 最典型的使用场景:
n 在复杂的UI交互过程中,保存用户操作过的界面元素以及模型数据的中间状态,记录用户的操作轨迹。(常常和Command模式一起使用)
l 这个模式,易于成为通用短语。 --但是,对沟通双方的实践经验要求比较高。
4.4 Observer 观察者
l 意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
l 要点:
n 将消息的接收者进行高度抽象,抽象为统一的Observer。这样可以获得一种非常简洁的方式让消息发布者向多个订阅者发送消息。
l Observer模式有两个显著的好处:
n 解除“消息发布者向消息接收者的类型的依赖”。
u 因为有了“Observer”这样一种抽象,可以使得发布者可以不知道观察者的具体类型,可以同时向多个不同具体类型的观察者发布消息。
n 可以方便地进行订阅者的动态维护。谁来订阅,俺就发布给谁;有多少订阅者,俺就发布多少份。
l 唠叨几句…
n 请注意,GOF说本模式的别名又叫“publish-subscribe(发布-订阅)”模式。我更喜欢这个别名。。。
n 嗯,这个模式被使用得非常广泛.有个跟Observer很相似的大家非常熟悉的字眼-Listener,应该也可以算是一种模式吧.(因为Listener没有出现在GOF里面,不好讨论它们是否有区别。我认为Listener与Observer几乎没有区别)
l 这个模式,易于成为通用短语。--如果对方不明白,请向他灌输这个模式,如果你不明白,请自己灌输。
4.5 State 状态
l 意图:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
l 要点:
n 将与具体状态相关的问题,交给每个具体的状态类实例去处理。
n 每个状态对象,可以接收的消息是一样的。
l 这个模式的应用范围(问题域)比较窄。而且我觉得很好识别:
n 当一个你所要描述的实体,具有很多状态;
n 而且这些不同的状态下,实体可能接收到的消息是一个固定的集合(对于发送消息的一方来说,它不管或者根本不知道接收方当前是什么状态)
n 不同的状态下,该实体接收到相同的消息时进行的处理往往有区别。
n 处理完某个消息后,往往伴随着状态的跳转。
n GOF举的两个例子,都完全符合上述条件:
u TCP协议栈,将连接状态看成一个独立的实体的话,它在任何状态都有可能收到网络彼端发来的关闭,打开,确认,同步等等请求,每个状态下处理这几个请求的动作都不一样,收到请求后,状态会改变。。。
u 交互式绘图程序,这方面我的认识很浅,在脑海里大概想象了一下,也符合上述条件。
l 这个模式,易于成为通用短语。
4.5.1 状态模式补充
l 这个模式的实现方式相对比较固定
n 设计模式的初学者可以找一个需要用本模式解决的问题来进行练习:
u 如何把设计模式应用到自己的设计、编码中去。
u 可以实践好几个相关的GOF设计模式:
l 状态
l 工厂方法(基本上会用到)
l 抽象工厂(可以用到)
l 模板方法(基本上会用到)
l “谁定义状态转换”这个问题值得注意
n 我个人习惯由各个具体的状态类对象负责状态的转换。理由是:“下一个状态是什么”,这个问题往往跟当前状态息息相关。
如果你这么做了,那么就同时也是在实践一个跟“中介者模式”相反的模式。中介者模式中,我们希望解除多对多的依赖关系,而这里,恰恰多对多才能将复杂问题分割并简单化。
n 反之,如果你发现“下一个状态是什么”这个问题跟当前状态无关,就可以选择由上下文来决定状态转换。
4.6 Strategy 策略
l 意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
l 我无法在本文中将此模式的要点进行简洁描述。
l 大概说一下我的体会。
策略,意味着:
n 对同一个问题,有多种相似算法或者解决方案。
n 需要某处担负“对这些策略进行选择”的职责。
n 为了更好的选择策略,可以让每个策略定义一个共通的“尝试”方法,调配策略时,让每个策略对象对Context进行“尝试”,根据“尝试”的结果来决定是否要选择该策略。
l 显而易见,本模式更适合解决复杂的算法问题。
l 这个模式,不易成为一个通用短语。
4.7 Template Method 模板方法
l 意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
l 红色是要点:
n 定义一个算法的骨架…(本模式的“意图”,很清楚的描述了本模式的要点)
u 我们是否可以认为,任何一个抽象类声明的纯虚方法(抽象方法),都是模板方法?
u 不是的。模板方法的要点,在于这些抽象方法是以什么样的方式被使用的:模板方法是在比较固定的场景以比较固定的形式被使用的。
u 这里所谓的比较固定的场景,固定的形式,即意图里面所说的“算法的骨架”,是一个复杂的问题域中的不易变的部分。而这些抽象方法的实现中,封装了问题域的易变的部分。
l 这个模式,易于成为通用短语。
4.8 Visitor 访问者
l 意图:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
l 是的,我觉得,Visitor模式的要点,在于访问者与被访问者是如何交互的,如图:
n anObjectStructor负责遍历所有的element和所有的visitor(一般会有多个Visitor,虽然上图中看不出来)。(由一个“对象结构”来负责遍历,我觉得貌似有点别扭。负责“遍历”的这个对象,如果换一个其他的名字,例如“组织者”,我会觉得很快乐。)
n 在遍历的过程中,每一个Visitor都将对每一个element访问一次。访问时,Visitor对element进行分析,并将得到的信息保存在一个Context里面。
n 注意accept方法,这个方法里面,用到了所谓的“双分派”技术(或者说,这仅仅是一个概念,但毕竟这种做法是很有特色的),双分派的概念在GOF原文的“实现”这一节有详细描述。
n 好吧,请再回头看看Visitor模式的“意图”这一节,“表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。”这句话我觉得太生涩拗口抽象摸不着头脑了。。。
Visitor模式的使用场景毕竟窄,但是,希望有人在面对类似“文本解释器,语法分析器,编译器”这种级别的问题时,能够想起这个模式。
l 这个模式,不易成为一个通用短语。
5 我的几大困惑
l 第一次学GOF
n 背景:不到一年开发经验,代码写了10K左右,没有做过设计。哪怕是详细设计。
n 学习效果:很差。看到第二章“实例研究,设计一个文档编辑器”,用了我一周大概20个小时,没看明白。后面的翻了一下。很多东西都没有留下印象。
n 效果差的原因:没有做过设计,里面讲的很多东西都无法领悟,所以记不下来。
l 第二次学习GOF
n 背景:做过设计了,设计得很糟糕…
n 学习过程:把GOF设计模式全文通读了一遍,还把书里面推荐的实现方法,自己用Java代码敲了一遍。
n 学习效果1:只是觉得,GOF他们4个家伙,把“高内聚,低耦合”的原则,在所有的设计模式里面体现了一把。
n 学习效果2:对于GOF里面提到的大多数(或者说,绝大多数)的设计模式:
u 既不知道到底该在什么时候用(书里面都写了适用场景,可是在实际项目里面,很难决定。不知道自己这里这么用,那里那么用到底对不对)。
u 也无法拿来跟人交流。不知道自己对这个模式理解的对不对,也不知道别人理解的对不对。例如:我跟人说“这个地方咱们用Builder模式吧。”,其实说这句话的时候,自己心里也没谱,不知道自己脑袋里面想的那几个类及其依赖关系和交换过程,是不是真的叫Builder。而且,说出来之后,从对方的眼神里明显的感觉到一片混沌。“Builder是啥…?”
u 当然,即使到现在,有些模式还是不知道自己理解的对不对。
n 学习效果3:终于自认为还是有几个模式用得比较滚瓜烂熟驾轻就熟,那就是单例,工厂方法,状态模式等等。(又过了很久才知道,原来单例也没那么简单。在进行J2EE的典型的分布式调用的时候,还得有特殊的机制保证单例的“单”。因为一般情况下,对象可以在远端被反序列化出来的。。。)
l 第三次学习GOF
n 背景:现在。做模块级别的设计即概要设计和详细设计有好几年了。系统级的设计,刚参与了一次。公司组织学习设计模式,我也早觉得有写一点东西的必要了 。
n 又把书仔仔细细读了一遍,终于自我感觉可以分辨大多数的模式之间的细微的差别了。(也许再过几年又会把GOF重新学一次,那时候会不会又把这句话重复一遍?)
n 交流时有时候可以从对方的眼神里判断出来,对方到底有没有明白我在说什么。。。但大概有一半的次数,还是不知道对方有没有明白。(这是情商问题?)
转自 http://blog.csdn.net/agipenia/article/details/5547858