《大话设计模式》简记
目标:可维护 可扩展 可复用 灵活性好
简单工厂模式
在计算器设计中,让工厂类根据所传入的符号,利用动态绑定的特性,来生成对应的运算类。
优点:简单工厂模式的最大优点在于工厂类中包含了必要的逻辑判断,根据客户端的选择条件动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。
策略模式
根据传入的具体策略对象,调用其算法的方法。实践中,在分析过程中听到要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化。
在商场商品销售软件的设计中,结合简单工厂模式和策略模式,让工厂生产一个所需要的具体策略对象,然后在策略模式类Context中调用具体的策略算法返回结果,实现客户端与收费算法的分离。
单一责任原则
在俄罗斯方块游戏中,将游戏界面,游戏逻辑拆分开来,可以拆分的类分开设计。
开发封闭原则
软件实体应该可扩展,但是尽量不修改。
依赖倒转原则
一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且它察觉不出父类对象和之类对象的区别。
装饰模式
小菜为自己见妹子,为自己搭配不同风格的衣服。服装类继承component类,并且让component作为Finery(服装)的一个成员,后面让具体的服装来继承Finery。为已有功能动态添加更多功能的一种方式,把每个要装饰的功能放在单独的类中,并让这个类包装它所要装饰的对象。有点类似于递归套娃。
代理模式
给娇娇送花,代理和真正的追求者都可以送花,送巧克力,送洋娃娃,真正的追求者让代理代替自己去做这些事。
应用有:远程代理、虚拟代理、安全代理
工厂方法模式
在薛磊风的案例中大鸟为小菜引入了工厂方法模式的概念。通过再客户端修改就可以创建出大学生志愿者和社区志愿者。
工厂方法将简单工厂的内部逻辑判断移到了客户端代码来进行,添加功能的时候简单工厂模式是修改工厂类,而工厂方法模式是来修改客户端了。工厂方法克服了简单工厂违背开放-封闭原则的缺点,又保持了封装对象创建过程的优点。
原型模型
大鸟通过指导小菜写简历,为小菜引入了原型模型的概念。
从一个对象再创建另外一个可定制的对象,并且不需要指导任何创建的细节,C++中的拷贝构造函数,可以看做是原型模型但是默认拷贝构造函数是实现的浅拷贝。可以通过为新对象重新动态分配空间,进行深拷贝,避免浅拷贝所带来的问题。可以声明一个私有拷贝构造函数,避免值传递时默认拷贝的发生。
注意拷贝构造函数与=运算符的区别:拷贝构造函数与=运算符的区别调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生。如果产生了新的对象实例,那调用的就是拷贝构造函数;如果没有,那就是对已有的对象赋值,调用的是赋值运算符。
模板方法模式
通过回答面试题,大鸟让小菜实现多人答题的情形,为小菜引入了模板方法模式,试卷就像一个超类,每个人只有答案部分是不一样的,初中时老师讲题目抄到黑板上,让大家再抄到自己的本子了。后来老师直接将题目给大家打印出来,让大家再试卷上写答案。这就是模板方式在现实世界中的一个映射。
将不变的行为搬移到超类中,去除子类中重复的代码,提高代码的复用,当不变的行为和可变的行为在子类的实现中混合在一起,可以通过模板方法模式将不变的行为移到单一的地方,帮助子类摆脱不变行为的纠缠。
迪米特法则
小菜第一天上班,IT部小张因为临时有事,耽误了为小菜配置电脑,而他又不认识其他人,导致浪费了时间。引入了迪米特法则:如果两个类不必彼此直接通信,那么这两个类就不应该发生直接的相互作用。如果其中一个类需要调用另外一个类的某一个方法的话,可以通过第三者转发这个调用。
外观模式
大鸟听到小菜的同事在买股票,通过为小菜普及股票和基金的概念,为小菜引入了外观模式。我们普通人虽然不是特别懂股票的k线图等专业知识,但是我们可以在普通用户和股票之间添加一个基金经理,让基金经理利用自己专业的知识负责投资。映射到软件设计中,对于一些设计粗糙高度复杂的代码,可以设计一个facade类,让facada与遗留的代码进行交互,新系统与Facada对象进行交互。
建造者模式
大鸟带着小菜去吃宵夜的机会,问小菜为什么肯德基麦当劳的食品的味道到中国的任何一个地方都是一样的,而中国的鱼香肉丝却是一个地方一个味道。通过让小菜画小人,中间小菜由于大意构造的小人丢掉了一条腿,而引出了建造者模式。记得刚开始学java时创建的接口类,不知道为什么要搞那么负责,直接定义好了拿来用不是方便吗,后面也意识到了接口规范的问题。再看这个地方的时候,这里与C++的纯虚函数的作用也是一样的,定义了继承类必须要实现的方法,对继承类形成一种约束,规范继承类的行为。
将一个复杂对象的构造与它的表示分离,是的同样的建造过程可以创建不同的表示。
观察者模式(发布-订阅模式)
小菜的同事们为了与老板斗智斗勇,让前台的美眉为其报告,当老板回来的时候就前台美眉打电话通知各个同事们注意老板回来了。
定义了一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象再状态发生变化是,会通知所有观察者对象,使他们能够自动更新自己。解除耦合,让耦合额双方都依赖于抽象,而不是依赖于具体,使得各自的变化都不会影响另一边的变化。
委托事件技术,是一样方法的类型,一旦为委托分配了方法,委托将与该方法具有相同的行为,委托方法的使用可以像其他任何方法一样,具有参数和返回值,委托可以看做是对函数的抽象,是函数的‘类’,委托的实例将代表一个具体的函数。那么委托在C++里面如何实现呢?可以使用C++11中的function和bind来实现C#中delegate 类似的功能。
function以及bind的使用:C++11 中的std::function和std::bind
抽象工厂模式
小菜加班很晚才回来,大鸟一问才知道,原来是公司因为业务需要,要将Sql Server数据库改为Access数据库,但是由于数据库更换,导致很多业务无法再继续使用,小菜不得不加班很晚才回来,大鸟了解到原因后开始帮助小菜分析业务问题,并在一步一步的指导下,为小菜引入了抽象工厂模式。
小菜想用工厂模式修改了数据库的访问,当意识到数据库中可能不止有一张用户表,可能还会有部门信息表,小菜意识到问题的存在,在大鸟的指导下完成了使用抽象工厂方法模式重新来访问数据库。
抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
但是抽象工厂也有优缺点:利用面向对象的多态,让具体创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名被具体工厂分离,不会出现在客户代码中。但是如果要在数据库中增加一个project表,就要增加三个类,同时还要改抽象工厂接口和具体工厂工厂类。
小菜给出了一个解决方案,添加上switch语句进行判断,就是使用简单工厂来替换抽象工厂模式,在客户端中得到数据库访问的实例,而不存在任何的依赖。
接着大鸟为小菜引入了反射+抽象工厂的解决方案,解决简单工厂的封闭性原则,避免了对程序的修改,开放扩展。然后使用配置文件解决要在程序内修改的问题。所用使用简单工厂的地方,都可以考虑用反射技术来去除Switch或if,解除分支带来的耦合。C++没有提供反射的库,但是可以通过一些技巧在C++中实现反射机制。
状态模式
今天小菜又是加班到很晚才回来,大鸟给他分析了一下加班的原因和情况。这个时候大鸟让小菜把加班的情况根据时间用程序表示出来,小菜按照面向过程的思路,写了一堆的if else判断时间,然后输出工作的状态。于是遭到了大鸟的嘲笑,在大鸟的指导下小菜首先将程序重构成了面向过程,但是成员方法一路写下来,导致方法很是复杂,并且没有遵循开放-封闭原则,如果要添加一个新的状态,要进行修改。通过将进行转换的状态拆分成一个一个的类,因此就引出了状态模式。
解决当一个对象状态转换的条件表达式过于复杂时的情况,将状态的判断逻辑转移到表示不同状态的一系列类当中,将复杂的逻辑简单化。
适配器模式
大鸟和小菜一起看过球赛后,谈论球赛时提到,姚明从CBA转到NBA的过程中,接受采访时姚明说:两个地方最大的区别是在CBA自己不需要翻译,但是在NBA自己需要。大鸟顺势为小菜引入了适配器模式,经过一段时间的学习,小菜对设计模式已经很是感兴趣。由于姚明自己不会英语,对于教练和队员的指令自己听不懂,只有让翻译翻译为自己能听懂的话后才可以,在这个过程中教练就是一个适配器。在生活中,既有两相插座,也有三相插座,如果某个地方只有两相插座,而自己的电器是三相的,就可以使用一个二相转三相的转换器来满足自己的需要。转化器就是中间适配器的作用。
但是适配器模式并不是提倡在项目的随意使用,对于项目的开始阶段,如果出现接口不统一的情况,采用适配器并不是最好的方法,而是应该重新协商定义接口,使得接口规范化。适配器模式更多的使用是在项目的后期维护,获取是前期要使用第三方的开发接口中。
大鸟最后引入的扁鹊治病的例子更是经典,如果能在早期事先预防接口不匹配的问题,何必事后再弥补呢。事后控制不如事中控制,事中控制不如事前控制。盲目使用适配器反而会本末倒置。
备忘录模式
在感叹人生无法重新来过之际,大鸟为小菜引入了备忘录模式。大鸟让小菜写一个游戏角色去跟boss决斗,但是在决斗前先将自己备份一下,如果在决斗中,没有成功的话还可以在备份点进行恢复,而不必从头来过的过程。小菜为游戏角色分配了各种成员函数,在客户端程序中用一个游戏角色的clone体来保存了游戏角色的所有数据(类似于致命游戏中那台机器)。但是这样给客户端分配的压力比较多,承担的责任也是比较多,所有的细节都暴露给了客户端。
备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并对该对象之外保存这个状态,这样以后就可将改对象恢复到原先保存的状态。Originator负责创建一个自己的备忘录Memento,让Caretaker来负责管理保存备份出来的备忘录Memento,Memento中有所有Originator要放属性的位置。
比较实用与功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator可以根据保存的Memento信息来还原到之前的一个状态。类似于快照。
组合模式
小菜因为要为公司开发一套办公管理系统,但是由于公司总部想要将这套系统推广到各个分公司,并且每一个分公司的机构都是类似的,要求总部、分部和办事处形成树状的机构,而不是简单的平行管理,大鸟提示小菜这是整体与部分可以被一致对待的问题。就是一个组合模式。看完组合模式以后我想到了系统中的文件管理系统,总公司就像是文件夹,一个一个文件就像是办事处,形成了一个树状的嵌套结构。
组合模式:将对象组合成树状形结构以表示‘部分-整体’的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。一种方法是透明式:叶结点和分支结点对于外界没有区别,具备一致性的行为接口。另一种是安全式:Component中不去声明add和remove方法,在Composite中声明用来管理对象的方法,这样客户端就要相应的判断,带来了不便。
迭代器模式
小菜与大鸟在坐公交车的时候,被售票员的专业性深深折服了,就是要被警察抓走的小偷,售票员也是能记住到底有没有买票。这时大鸟又顺势引入了迭代器模式的概念。在C++的STL中,迭代器作为六大组件之一,起着非常重要的作用,扮演了容器与算法之间的粘合剂。
迭代器模式:分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,又可以让外部代码透明地访问集合内部的数据。
单例模式
小菜在公司写了一个MDI窗体程序,要实现一个工具箱窗体,但是小菜在写完以后发现自己的程序每点击一次就会出现一个窗体,不符合自己的要求。这时大鸟通过计划生育的问题为小菜引入了单例模式,将构造方法进行私有化,但是提供一个公有的方法来供外部获得到实例。
单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
通过使用双重锁机制,可以在多线程中保证只有未被创建实例的时候才加锁。如果是在第一次引用时才将自己实例化,被称为懒汉式单例类。如果是在加载时就将自己实例化,则被称为饿汉式单利类。
桥接模式
大鸟买了一款新手机,在玩小时候就的经典闯关游戏“魂斗罗”,小菜想要把这款游戏也装到自己的手机中,但是由于他们两个人的手机不是相同的品牌,无法安装这个游戏。但是在pc端,硬件厂商和软件厂商却因为微软的存在发展的如火如荼。大鸟让小菜自己用程序实现在不同手机品牌上运行不同软件的程序,小菜通过继承的方式,让不同的手机品牌可以运行不同的软件,但是这个时候大鸟让小菜添加一个MP3播放软件,小菜不得不在每个品牌下添加一个子类,显得很是麻烦。这个时候小菜将结构图改为了手机软件作为基类,手机品牌来继承软件类,但是这个时候要是增加一个手机品牌有成了一个很麻烦的事情。大鸟接着为小菜介绍了一个很重要的原则:合成/聚合复用原则,尽量使用合成/聚成,尽量不要使用类继承。两个翅膀合成大雁,多个大雁聚合为雁群。在继承时一定是“is-a”的关系时再考虑使用。
桥接模式:将抽象部分与他的实现部分分离,使他们都可以独立地变化。也就是,实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多角度分离出来让他们独立变化,减少它们之间的耦合。
命令模式
晚上小菜和大鸟到烧烤摊前面吃烧烤,但是由于人员太多,导致烧烤摊老板记账混乱,发配烤串混乱,大鸟带着小菜到了一个烧烤店,服务员过来为他们要点什么,大鸟连菜单都没有看就点好想要的东西,这时大鸟开始问小菜,有没有发现这里与烧烤摊的不同。在烧烤摊我们是直接与烧烤员交流,这里我们是与服务员交流,最后服务员将我们点的告诉烧烤员。这时大鸟感觉他们点的羊肉串有点多了,告诉服务员他们只点6串,服务员在本子上写写画画。让后通知给了烧烤员。大鸟让小菜将刚刚烧烤店的模式用代码写下来。吃着烤串小菜完成了基本的功能。经过大鸟提示,告诉小菜,让服务员来负责下命令发号施令。服务员来请求烧烤者的方法所抽象出来的类。
命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。可以容易的设计一个消息队列,容易将命令计入日志,允许接收请求的一方决定是否要否决请求,容易的实现对请求的撤销和重做,加具体行的命令类不影响其他的类,增加新的命令类也很容易,把请求一个操作的对象与知道怎样执行一个操作的对象分隔开。
责任链模式
小菜在公司工作三个月了,就要转正了,并且小菜在大鸟的教导下学习了很多的设计模式,通过学到的东西解决了公司的不少业务问题,于是小菜向经理提出加薪的请求,但是经理没有权利,只能带着小菜去找总监,但是总监也没有这个权利,又带着小菜找到了总经理。结果总经理直接拒绝了小菜的加薪请求,声称大学毕业生这么多,还没有在这么短时间内加薪的,小菜很是郁闷的找到了大鸟,大鸟对于总经理这种理由也是十分愤慨。于是大鸟让小菜将这个加薪的过程用代码展示出来。小菜将这个过程抽象出了请求类和管理者类,写完以后,小菜自己也意识到了自己写的管理者类由于添加了太多的判断分支导致非常的繁琐,然后分析出违背了单一责任原则和开放-封闭原则,于是大鸟为小菜引出了责任链模式。
责任链模式:多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这个对象连成一条链,并且沿着这条链传递该请求,直到有一个对象处理它。
链中的对象自己不用知道链的结构,可以简化为对象的相互连接,仅需要保持一个指向后继的引用,不同保持它的所欲候选者的引用,大大降低了耦合度。
小菜根据责任链模式重构完程序后,手机突然响了起来,原来是经过经理的建议,总经理决定给小菜加薪了。
中介者模式
小菜坐在客厅问大鸟,今天学什么模式呢?大鸟直接回答说:中介者模式,最近伊拉克发生了多起爆炸案,给人类带来了太多的伤痛,各个国际利益和政策都不同,如果有一个组织,能负责中间的调停那是不是很好呢。小菜回答道,那是不是指的是联合国组织。联合国组织的建立,为世界和平带来了不可估量的贡献。
中介者模式:用一个中介对象来封装一系列的对象交互,中介者使得对象不需要显示地相互引用,从而使得其耦合松散,而且可以独立地该表他们之间的交互。通过在客户端为每一个国家声明中介,将每一个国家在中介中进行一个声明,这样后期每个国家就可以通过中介来进行信息交互。
但是,很明显,这加大了中介者的复杂度,减少了各个国家之间的耦合度。当系统出现多对多交互复杂的对象群时,不要急着使用中介者模式,而是反思在系统设计中是否合理。
享元模式
小菜最近每天回来就自己忙自己的,于是大鸟问小菜在忙什么,小菜说自己接了一些外包项目,给客户做产品展示网站,但是最近他们的朋友也都找上门来要做相同的网站,但是有一些差异,小菜正在头痛
怎么办呢,大鸟一看小菜写的代码,每一个网站实例都进行了实例化,但是有些网站其实是很相近的,于是大鸟给小菜介绍了享元模式。
享元模式:运用共享技术有效地支持大量细粒度的对象。
看完大鸟的介绍后,小菜开始重构自己的代码,使用共享的方式来生成对象,大大节省了内存资源的开销。在程序设计中,如果发现大量细粒度的类实例要来表示数据,除了几个参数外基本上都是相同的,如果能将那些参数移动到类实例的外面,在方法调用时将他们传递进来,就可以通过共享大幅度来减少单个实例的数量。比如在围棋游戏中,整个棋盘上每个棋子只有位置是不同的,内部只有黑白两色,坐标就可以是棋子的外部状态。
解释器模式
小菜回来后与大鸟说,自己被老板叫到办公室,老板夸了自己表现出色,还说梅星同事是个普通员工,大鸟听后,对小菜说老板这是有弦外之音呀,可不是你听到的那么简单。今天来讲一下解释器模式。
解释器模式:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表达式来解释语言中的句子。正则表达式就是解释模式的一个应用。
访问者模式
晚上九点,小菜在客厅念叨着男人成功时。。。女人成功时。。。大鸟听过到后问小菜在发什么神经呢,原来是小菜在网上看到的段子,感觉挺有意思,就抄了下来。大鸟想要给小菜讲最后一个访问者模式,但是小菜还是不停念叨男人女人的,突然大鸟听到小菜说了一句,人类在性别上只有男人和女人两类人。大鸟顺从小菜的意愿,将访问者模式与男人女人一起来讲。
访问者模式:表示一个作用于某对象结构中的各元素的操作,它使的你可以在不改变各个元素的类的前提下定义作用于这些元素的新操作。
小菜按照大鸟画的UML图,拆分出了状态抽象类,人抽象类。通过双分派,在客户端中将具体状态传递给男人,然后男人类调用具体状态的方法,并将自己作为参数传递给具体状态。
访问真模式适用于数据结构相对稳定的系统,目的是为了把处理从数据结构分离出来。当系统有比较稳定的数据结构,又有易于变化的算法时,使用访问者模式就是比较合适的。缺点就是增加新的数据结构变得困难。
最后,如书中所讲,生活还在继续,编程也不会结束。