(一)
一开始我打算好好研究常见的几种设计模式,搞清楚他们各自适用的场景,方便以后懂得运用在项目中,让代码更优雅。复习了代理模式、适配者模式、工厂模式、建造者模式、装饰者模式、观察者模式后,我才明白——
23种设计模式按照使用目的来分,可以分为创建型模式、结构型模式和行为型模式。创建型模式用来处理对象的创建过程;结构型模式用来处理类或者对象的组合;行为型模式用来对类或对象怎样交互和怎样分配职责进行描述。
在项目经验不足的情况下,一定要多静下心思考各种模式本质本质本质解决的问题到底是什么。看清自己的实力,对自己要求高一点。在现实开发中,对于不同类型的代码,质量要求是不一样的。包括时间,资源,需求易变程度,复用程度和稳定性,人员素质,决策层的关注度等。很多时候是不能强求的……既不能向脏乱差随便妥协,也不能把自己项目搞成分析瘫痪。
熟练了解常见的几种设计模式是必要的,这是一种工作语言,总不能同事说:“用策略模式吧”,然后你不知道这是什么东西。浪费大家的时间。设计模式可以起到便于沟通的作用,交流成本有时是很高的,尤其在程序员之间。
不是解决任何问题都要从头做起。往往我们更愿意复用以前使用过的解决方案。
(二)
好的,我们接着说说创建型模式、结构型模式和行为型模式底下都有些什么——
创建型模式用来处理对象的创建过程,主要包含以下5种设计模式:
工厂方法模式(Factory Method Pattern)
抽象工厂模式(Abstract Factory Pattern)
建造者模式(Builder Pattern搜索),将一个复杂的构建过程与其表示细节相分离,使得相同的构建过程可以创建不同的表示。
原型模式(Prototype Pattern),通过拷贝原型创建新的对象。
单例模式(Singleton Pattern),保证一个类只有一个实例。
结构型模式用来处理类或者对象的组合,主要包含以下7种设计模式:
适配器模式(Adapter Pattern),使得原本由于接口不兼容而不能一起工作的那些类 可以一起工作。
桥接模式(Bridge Pattern),两个维度独立变化,依赖方式实现抽象与实现分离:需要一个作为桥接的接口/抽象类,多个角度的实现类依赖注入到抽象类,使它们在抽象层建立一个关联关系。
组合模式(Composite Pattern),用户对单个对象和组合对象的使用具有一致性的统一接口。
装饰者模式(Decorator Pattern),保持接口,增强性能;修饰类继承被修饰对象的抽象父类,依赖被修饰对象的实例(被修饰对象依赖注入),以实现接口扩展。
外观模式(Facade Pattern),在客户端和复杂系统之间再加一层,这一次将调用顺序、依赖关系等处理好。即封装底层实现,隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的高层接口。
享元模式(Flyweight Pattern),享元工厂类控制;HashMap实现缓冲池重用现有的同类对象,如果未找到匹配的对象,则创建新对象。
代理模式(Proxy Pattern),为其他对象提供一种代理以控制对这个对象的访问:增加中间层(代理层),代理类与底层实现类实现共同接口,并创建底层实现类对象(底层实现类对象依赖注入代理类),以便向外界提供功能接口。
行为型模式用来对类或对象怎样交互和怎样分配职责进行描述,主要包含以下11种设计模式:
责任链模式(Chain of Responsibility Pattern),拦截的类都实现统一接口,每个接收者都包含对下一个接收者的引用。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
命令模式(Command Pattern),将"行为请求者"与"行为实现者"解耦:调用者依赖命令,命令依赖接收者,调用者Invoker→命令Command→接收者Receiver。
解释器模式(Interpreter Pattern),给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
迭代器模式(Iterator Pattern),集合中含有迭代器:分离了集合对象的遍历行为,抽象出一个迭代器类来负责,无须暴露该对象的内部表示。
中介者模式(Mediator Pattern),对象与对象之间存在大量的关联关系,将对象之间的通信关联关系封装到一个中介类中单独处理,从而使其耦合松散,可以独立地改变它们之间的交互。
备忘录模式(Memento Pattern),通过一个备忘录类专门存储对象状态。客户通过备忘录管理类管理备忘录类。
观察者模式(Observer Pattern),一对多的依赖关系,在观察目标类里有一个 ArrayList 存放观察者们。当观察目标对象的状态发生改变,所有依赖于它的观察者都将得到通知,使这些观察者能够自动更新(即使用推送方式)。
状态模式(State Pattern),状态对象依赖注入到context对象,context对象根据它的状态改变而改变它的相关行为(可通过调用内部的状态对象实现相应的具体行为)。
策略模式(Strategy Pattern),策略对象依赖注入到context对象,context对象根据它的策略改变而改变它的相关行为(可通过调用内部的策略对象实现相应的具体策略行为)。
模板方法模式(Template Method Pattern),将这些通用算法抽象出来,在一个抽象类中公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。
访问者模式(Visitor Pattern),
参考:九种常用设计模式的应用
(三)
在知乎上看到一个回答,写的特别棒。为了方便大家阅读灯,我在这贴出来,侵删。
问题:如何正确地使用设计模式?
作者:kidneyball
链接:https://www.zhihu.com/question/23757906/answer/25616658
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
首先离题一下说点虚的其实GoF的《设计模式》一书,一共有三个层面的内容:
1、指出编程开发活动中存在模式,提出总结设计模式需要关注的四要素 "名称-问题-解决方案-效果“ ,并给出描述一套模式的格式模板。
2、提出了面向对象开发中”针对接口编程优于针对实现编程”,”组合优于继承”的总体设计思路 。
3、选取了现实开发中基于上述设计思路所形成的23种常见设计模式作为例子详细描述虽然第3点包括的多个具体的设计模式实例占据了最多的篇幅,但事实上第1,2点才是纲。实际上《设计模式》一书最划时代的意义,在于第1点。在此之后,出现了以设计模式的格式来组织内容的《分析模式》,《企业架构模式》,《企业集成模式》,《xUnit测试模式》,《重构》等等质量颇高的书籍。
在书中有一段我认为非常重要但很容易被忽略的话:
本书中涉及的设计模式并不描述新的或未经证实的设计,我们只收录那些在不同系统中多次使用过的成功设计。这些设计的绝大部分以往并无文本记录,它们或是来源于面向对象设计者圈子里的非正式交流,或是来源于某些成功的面向对象系统的某些部分,但对设计新手来说,这些东西是很难学得到的。尽管这些设计不包括新的思路,但我们用一种新的、便于理解的方式将其展现给读者,即:具有统一格式的、已分类编目的若干组设计模式。
这段话的关键是:
1、书中的模式不是作者的发明创造或独门秘籍,而是早已存在并已经广泛使用的做法,只不过没有被系统地加以记录。换而言之,只要遵循某些原则,这些所谓模式完全可能在无意识的状态下自发出现在产品代码中。
2、这些模式在各种系统被多次使用。换而言之,你只要接触足够多的代码,必然会大量接触到这些模式的实际应用。只不过在看过《设计模式》一书之前,你可能意识不到这是一个成体系的设计手段。
3、作者认为《设计模式》这本书的价值在于对设计模式进行了有效的组织和便于理解的描述。换而言之,这本书的写作出发点是”便于理解“,并且是面向”设计新手“的。而不少初学者却恰恰觉得这本书难以理解,这说明,作者已经在保证准确性的前提下,选用了他们所认为最便于理解的描述。比本书描述更为显浅的描述,很可能会牺牲准确性(不准确的描述对于新手来说是显然是害处大于好处)。当然某些人认为是作者表达能力有限,这种事情无法求证,但我倾向于前者。
现在开始正题,如何正确使用设计模式
准确来说,正确使用GoF的《设计模式》一书中所涉及的23种模式。但我们不妨先考虑一个比较容易回答的问题,如何避免不正确地使用设计模式。毕竟不正确地使用还不如不用,你在避免不正确使用的前提下慢慢用起来,就是正确使用了。
使用设计模式最常见走入歧途的做法是:你看了《设计模式》中某个具体模式,觉得似懂非懂,或者甚至根本没看过原书。然后去看了一些举例子打比喻的”再谈“,”妙解“,”大话“之类的东西,觉得豁然开朗。之后在开发时发现某一处非常符合你对这个模式的理解,于是开始使用。这种做法用来练手可以(也就是你明知使用这个模式未必是一个好主意,只是为了尝试在现实场景中去实现一下,练习代码并不进入最终产品代码),用来做真实的设计则往往谬以千里。
原因很简单:设计模式是一种驾驭抽象概念的技术,而描述模式的标准格式里就包括了抽象描述,代码示例和应用场景。如果一个程序员根据这些信息还不能理解一个设计模式的话,说明他首先抽象思维尚不足以驾驭设计模式,其次在理解代码和接触应用场景方面经验不足。简单来说,还未能达到“设计新手”的入门水平。在这种状态下勉强去使用设计模式,出问题是在所难免的。
因而得出第一点:如果你已经看过某个设计模式的描述,要正确使用它的最基本前提是,你能完全看懂GoF《设计模式》中对它的描述。在此之前,只看,不用。看是指看该模式的原文描述或者具体代码。特别地,不建议通过一些类比具体生活例子的方式来理解设计模式。设计模式的写作格式是经过验证,能有效地描述一个模式而又不失准确性的手段。如果你无法理解,看实际生产代码的应用场景是唯一真正有效的方法(因为设计模式本身就是从具体代码中总结出来的)。用类比的方法降低设计模式的抽象性来帮助了解没有实质的意义——即使你觉得自己懂了,你的抽象思维和开发经验还未达到能正确使用这个模式的水平。
正如前面所言,只要你对面向对象一些基本原则有充分的理解,你甚至可能在没看过《设计模式》之前就开始使用某种模式了,如果你已经达到这种程度自然能无压力看懂描述。退一步假如你还没达到这种程度,既然《设计模式》中的模式非常常见,你只要有心多看代码,在现有代码中必然能接触到。通过实际应用的代码与书中的描述互相印证,要理解亦不难。再退一步,假如你接触的代码就一直没遇到 某个模式,你也一直无法自发理解某个模式,那么这个模式就对你没用,你没必要一定要找机会用。
避免不正确使用的第二点是,避免过度设计。这里说的过度设计本质上就是你为可能发生的变动支付了过多的复杂度代价。其实过度设计和设计模式没有必然的关系,只要你认定一个地方会变动,你就会考虑是否应该增加复杂度来换取灵活性。设计模式只不过针对某些具体场景提供了一些效率较高的以复杂度换灵活性的手段而已。避免过度设计的关键是,你能正确评估未雨绸缪所引入的复杂度,相对于发生变动的可能性和破坏力,是否值得。
正确评估变动的可能性和破坏力,只能依靠行业经验,属于资历问题。如果你对当前场景没有足够的经验进行评估,最好的办法就是假定它不会频繁变化,只采用普通的高内聚低耦合策略,而不需要增加额外的复杂度来提供灵活性。等到确认出现变化时,再进行重构。
而对设计模式的认识可能会影响对复杂度的估计,不少设计模式的初学者很容易错误估计实现某个设计模式所带来的复杂度,认为灵活性随手可得。又或者下意识地寻找练手机会而忽略掉复杂性的代价。在假定程序员对某个设计模式已经充分理解的前提下,我觉得评估复杂度时至少应该考虑到这些因素:
1、需要额外引入哪些新的概念。要明白一段代码涉及的概念越多,就越难理解。
2、设计模式自身的实现复杂度。
3、一旦引入后,为了保持设计所带来的灵活性,后续开发需要注意的地方。是否能通过代码层面的限制来保证灵活性不会被破坏。
4、团队中其他人对这个设计模式的理解程度。
5、对排错调试,代码静态分析可能造成的影响 (例如Observer模式和Visitor模式往往会打乱静态分析,难以通过阅读代码确定执行状态)如果能够大致准确地评估上述要素然后作出决定,我觉得即使变动最终没有发生,也算是一个合格的设计决策。真正的难点在于评估变动,这只能靠经验。还有就是每次做出设计决策后最好能跟踪总结,为下次决策积累经验。关于设计模式的使用暂时想到这些。
既然题目中提到了“设计模式的荼毒”,这里也说说我认为《设计模式》一书中最大一处问题:一句看上去正确,但被后来一些读物误解并放大,在实际执行中造成最多问题的话:
命名一个新的模式增加了我们的设计词汇。设计模式允许我们在较高的抽象层次上进行设计。基于一个模式词汇表,我们自己以及同事之间就可以讨论模式并在编写文档时使用它们。模式名可以帮助我们思考,便于我们与其他人交流设计思想及设计结果。
这句对“模式名称”要素的描述的话,在很多后续书籍或文章中被引申为:设计模式的一个重要作用是为团队提供了一套方便的交流手段。看上去非常正确,例如,我可以对同事说,这里需要一个Adapter;或者在代码中直接命名XXXApapter,同事们一看就知道这是什么了。交流变得非常方便——前提是,我们都看过《设计模式》并清楚关于这个设计模式的描述。使用设计模式进行交流的结果就是:了解某个设计模式的人跟不了解这个设计模式的人根本无法交流。
而交流在团队中是一种非常基础,不可或缺的东西,进一步的结果就是,了解某个设计模式的人认为不了解这个设计模式的人达不到基础水平。而按照前文的分析,设计模式只不过是对已有开发手段的总结,完全有可能出现某个人的能力已经足够自发使用设计模式,只不过因为没认真看过《设计模式》这本书,而被认为达不到基础水平。这造成了很多有一定编程能力的开发者对设计模式十分反感。
再一步引申的结果是,因为设计模式变成了一种鉴别是否具有基础水平的手段,那么为了让自己看起来有基础以上水平,就必须要表现得懂设计模式——即使看不懂《设计模式》原文。这就给许多“大话”,“再谈”读物带来了市场,进而造就了一大批不是从实际开发或阅读代码中理解设计模式,在实际应用中错漏百出的初学者。