读书笔记之——设计模式

设计模式

读书笔记系列

觉得读书实在没什么可做笔记的,以为理解,记住就是看书的全部。可是看着看着发现好多不容易理解的地方,情不自禁地会写下了好多笔记在本子上,那就转抄一下到博客上。

写博客有时候会遇到一个知识点,当时没太看懂,含糊记了个概念,但要记录在博客上又不可含糊,就回头翻阅书籍和文章把知识点啃下来。其次在转抄的过程中,读过的内容又会在脑海中重现一边,加深第一边读书的印象和效果,所以…读书做笔记并且分享出来,是有好处的,虽然花的时间稍微会长一点,读的页数也会少一点,但还是不应该盲目追求速度,尤其是对于那些难啃的书,做笔记写博客都是有意义的。

以此为系列序。

一、序

读设计模式的意义在于,一个是深入理解面向对象,在设计中难免会遇到类似的问题;再一个,相信读一些优秀的代码的时候会遇到类似的设计,能够更快速的吸收和理解。

二、创建型模式

1、抽象工厂(ABSTRACT FACTORY):
本模式的重点在于创建实例的方式。通过工厂类的接口创建具体的产品。优点在于可以快速切换实现,修改工厂类实现即被切换,缺点是每种产品都需要一个工厂类进行支持,哪怕只有一点点修改,还有新产品的接口稍有不同并不好直接支持(需要加入其他的模式),或者说限定了接口。

2、生成器(BUILDER):
所以,和抽象工厂不同,抽象工厂会返回具体的产品,而生成器模式是通过操作生成器的接口创建复杂的组合产品。将具体的产品接口封装在生成器接口里,通过操作生成器间接创造产品类,间接操纵产品类,最后返回一个复杂的复合产品。它可以向用户屏蔽产品的具体实现,将产品和实现分离,主要用于生成符复合产品。

3、工厂方法(FACTORY METHOD)
这个我觉得和抽象工厂好像。
我觉得他们之间的主要差别在于具体的场景稍微有点不同,抽象工厂一般是多个类中选择一个,方便切换配置使用;而工厂方法,顾名思义是一种具体方法,主要用于将实际在使用的A类生成对应B类的场景修改成,A类可以通过一个方法生成继承至C类的B类的一种方法。
所以抽象工厂实际上就是使用了工厂方法,来提供一种全局唯一的底层支持抽象(如:窗口是使用Windows系列的工厂还是Qt系列的工厂)。

4、原型(PROTOTYPE)
一个生产类,不需要子类就可以生成产品子类。主要是通过保存产品子类的指针,然后通过子类提供的clone接口进行复制提供产品实例。
优点很明显,一个需要设计的类只有具体产品类,没有工厂类;再一个可以动态的增添实例以达到动态添加支持的子类的效果。
缺点也很明显,如果使用第三方产品,不一定提供clone方法(需要配合使用其他的设计模式才可以使用本模式)。

5、单例模式(SINGLETON)
保证唯一的全局访问点。不使用静态变量或者全局变量(后称静态变量)是因为,使用静态变量不能保证只能唯一创建一个,其次若多个单例创建顺序之间有依赖,编译器不能解决全局变量定义的依赖问题,另外还有就是没被使用到的也会被创建。
单例模式可以避免上述所有情况,但书中没提到单例常见的线程安全的问题,说明网上很多讲单例模式的博文里提到的那本设计模式和我读的不是同一本,心累。

三、结构性模式

1、适配器(ADAPTER)
顾名思义,将原本不能与统一接口使用的产品(类)适配成可以使用的产品。有两种模式:
一种是类适配器,公有继承统一接口,用私有方式继承待适配的产品;
一种是对象适配器,公有继承统一接口,用成员指针变量的方式保存待适配的产品;
都是不难理解的概念,另外书中对本模式提到了双向适配,使原来不兼容的两个系统的产品可以相互使用,书中没展开说,我稍微说一下。要适配两个系统的不同的产品,但实际使用适配器时一个实例只代表一种产品,因此需要开发两个类(A适配B,B适配A),如果使用同一个类,需要一个标记变量表明现在实际产品是哪个接口继承的子类实例。

2、桥接(BRIDGE)
本模式主要可以将抽象部分与它的实现分离。
不希望抽象类与实现存在固定绑定关系,即不将实现类直接继承接口类。而是将实现类和接口类拆分成两个基类,一般实现基类实现基本操作,接口基类使用基本操作提供统一的、较高层次的操作接口。可以实现接口和实现的分离和在线切换,降低接口和实现编译的依赖性。

看上去与工厂和生成器模式有些类似,但那些模式重在创建一个实例,而不关注接口和实现的关系和结构。本章(结构性模式)的设计模式重在这一部分,本模式尤甚。

3、组合(COMPOSITE)
本模式将对象递归组合成树表示,可另单个对象和组合对象使用拥有一致性,使接口可以无需区分他们进行透明的编程,无缝切换,一般设计时需要最大化父类接口。
此处一般有两种设计,一个是父类接口可以直接组合对象,另一个是父类接口不可以直接组合对象,前者的优点在于简单统一,提供透明的接口,缺点在于会浪费叶子类或单个对象的内存。另一个优缺点相反。
另外此模式在使用时要考虑构建的方法(直接new还是工厂)以及释放内存的方法。
但据说应用广泛。

4、装饰(DECORATOR)——又名包装器(Wrapper)
能够动态地给一个对象添加一些额外的职责,比继承子类要灵活。
通过继承公共基类,使其拥有一个基类对象的同时还拥有基类的接口,能在转发每一个请求给拥有的基类对象前后执行某些操作,能够让基类直接装饰类成为一般子类的替代品,并且一个装饰类可以装饰所有子类。

5、外观(FACDE)
为各个子系统提供一个统一一致的界面,可以为若干类的组合操作提供一个默认顺序和参数。
总之可以简化复杂的子系统的接口,让用户不关心子系统的细节。

6、享元(FLYWEIGHT)
复用可复用的实例减少内存/计算开支,例如英文文字,一共有4种字体,二十万字的文章,在打印这种文档的时候,如果每个字母打印的图像都需要计算一次,那么开销很大,因此可以通过外部的环境(字体,字母)公用内部的数据(打印图像),对同一种字体的字母,公用对象内存,计算一次图像即可。
有一点类似于智能指针,在删除时也要用智能指针的方式去思考。
另外本模式的创建必须是由工厂类创建,才可以保证全局共同的访问点实现合理的共享。

7、代理(PROXY)
为其他对象提供一种代理以控制这个对象的访问,和装饰类似。可以用在消耗大的地方,延迟创建时间,或者用在网络上。
但和装饰不同是,代理拥有的是子类而装饰拥有的是父类,每个代理针对一个具体的类(一般是子类,当然父类也是可以的)提供延迟创建等转发机制。

四、行为模式

看到这更加感觉如第一章那幅图所描述,整本书各个模式之间是有相同性,每个章节之间的区别在于注重点不同,本章注重于信息的传输,如何保存转发请求。

1、职责链(CHAIN OF RESPONSIBILITY)
本模式是为了在线实时确定请求处理是由哪个实例,组合方式类似于COMPOSITE,子类会拥有父类并形成树结构,不过就像本章我开头写的,他们的注重点不同。COMPOSITE注重这种组合方式,而本模式注重树形结构之间消息的传递,所以本模式中父类会直接拥有子类(COMPOSITE模式中,这是其中一种组合形式),并且消息通过链进行传递,每一层要么处理请求,要么继续向下传递请求。
也可以定义成树,那样就会有许多if..else。

2、命令(COMMAND)
将界面和功能分离的模式,本模式设计的类作为功能,就是让界面的对象拥有命令类,使各种的界面拥有一个命令类(当然需要某种形式的工厂)便可以很方便的将界面和功能分离,多个界面可以共用一个命令,有些界面有独立的命令,包括快捷键出发的命令,都可以拥有同一个对象实例来实现。
一般撤销也是命令类的一部分,每个命令类拥有excute和undo两个接口,那么撤销命令只需要一个命令类队列即可。

3、解释器(INTERPETER)
看起来很像C++ Primer第四版”15.9 再谈文本查询示例”那节设计的那个程序所使用的核心思路,现在想起来那个程序所使用的就是本模式和C++的符号重载。

这本书看到这里,我真的觉得各个模式大致都是差不多的,只是父类成员是否拥有自身,或者行为上的执行方式执行顺序的细微差别,但都大同小异。
虽然本笔记的笔记本来就很简略了,因为省略了图和书上各种例子,但之后我可能会更加省略,简单介绍一下书中引入模式的例子,然后应用场景和自己的想法就好。
你们看不明白或者不想看,那点查查咯。

子类必须有一个叶子结点,用于分析单词的,其余子类都拥有父类,使用于解析各种组合;本模式主要使用在语法树上,在不追求编译速度的情况下使用很好。
复杂的文法不适合使用本模式维护。

4、迭代器(ITERATOR)
不暴露对象内部表示,但能提供顺序访问容器对象中各个元素的方法。
因为…STL中几乎所有容器都提供,就不写了。

5、中介者(MEDIATOR)
简而言之,将网状通信图收敛成星状,分为中介者和同事两种对象,中介者负责和用户以及同事交互,同事只与中介者交互,同事若想与同事交互必须通过中介者。组合方式和外观(FACDE)有一些相似之处,但区别主要在于可以双向通信。

6、备忘录(MEMENTO)又名Token
用于在不破坏封装的情况下,保存一个对象的内部状态。将原生类作为工厂类,可以通过创建方法产生备忘录对象实例,然后保存备忘录实例即可实现恢复状态。

7、观察者(OBSERVER)又名发布-订阅(Publish-Subscribe)
定义的是一种方法,用于订阅某个对象的变化,通过异步通知的方式更新,使得类之间的依赖变为接口之间的依赖,相互可以灵活变化。
被观察者父类处理好供给观察者使用的接口和成员,之后所有子类不用再处理这一部分,专注于功能/数据的实现。观察者负责获取/等待具体的事件,因此与被观察者子类协同工作。

8、状态(STATE)
和名字一样,用于描述状态(如TCP状态),没啥用,反正我觉得没啥用。
和一般的枚举类型不同,这个模式用子类来描述状态。

9、策略(STRATEGY)
终于到这一个模式,整本书很多地方都在提这个模式,之前在腾讯实习的时候,km上讲模式第一个也就是这个模式,作者说非常常用/非常有共鸣。
这个模式定义一系列算法,将它们封装起来,使得算法可以独立于使用的客户替换。
不过本书读到这个位置,依然觉得全书各个模式都是相通的,本模式亦然,没什么特别值得介绍的地方,和很多模式相似。
即客户使用的类拥有某个实现类基类对象,客户使用这个类会将请求转发至具体算法实现类实现,主要的特点还是可以通过简单的一行执行接口,会将请求转发自不同的类,实现不同的需求,确实还是很实用的,不同类别的需求,可以通过STRATEGY实现。

10、模板方法(TEMPLATE METHOD)
和观察者(OBSERVER)的设计有一些像,将不变的功能放在父类实现,子类专注于实现具体的不同的需求。
父类中会调用子类的函数(通过虚函数或者函数绑定)。

11、访问者(VISITOR)
和备忘录有一些相似,不同的在于本模式不仅适用于备份,而且可以扩展用于各种功能。需要对一个对象进行很多不相关的操作,但不希望让这些操作“污染”类的代码,本模式可以帮助你把操作集中定义在一个类中,每个VISITOR模式仅包含一些需要用到的相关的接口。
若数据类A需要某种操作,则定义操作类B,将A传递给B,让B来操作A。B即为VISITOR类,因此A类可以专注于数据相关的接口,高级接口均由VISITOR实现,又和几个模式很相似…A.dosome(B)改成A.dosome(C)就改变了具体的方法。

五、读后感

总体来说,深入了解了一些脱离于语言的面向对象的设计方式,并且最大的读后感就是觉得对面向对象和大型软件有深入一点点的了解。
书中几乎所有的模式都是本着库和软件,接口和实现要分离,相互影响尽可能地小但又要满足需求出发,立足于具体实现的各种模式。但在我看来,与其分为21种模式不如变成这一个思想:设计是让库和软件,接口和实现尽可能地分离,可以独立变化的模块尽可能地细,模块之间的耦合尽可能地小
这是本书最大的读后感。

另外后面的阅读包括理解可能有一些偏差,因为在家抽出零散的时间读的,也没有系统的做笔记。没有大块连续的时间,总是会忘记前面读了什么,不会去联想后面会怎么写,所以阅读效果会差一些,但是我觉得我读到了本书的精髓,所以也没打算回过头把在家看的那些再重看一遍。

得益于书中大量的实例,每个模式至少一个实例,而实例应该是具体软件的设计细节,这么多软件设计实例比书总结出来的设计模式更让我喜欢。理由很简单,他们根据这些实例总结出的设计模式确实好用。但我们根据这些实例可能能总结出相同的设计模式,也可能总结出一些不太一样的东西。与其看他们总结出来的东西,不如看他们如何总结的,而这本书很清晰地告诉我们模式来源的思路。授人以鱼不如授人以渔,这本书做到了这一点

虽然大家读得都是head first设计模式一书,因为这本书相比四人组的书新很多,会包含更加现代的一些考虑在其中,但短时间不会再读Head first的设计模式一书,之后再做一些东西之后回过头去读一读head first的那本吧。

1 评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注