设计模式启示录 (二)
设计模式启示录(二)
如需转载请注明出处:http://blog.****.net/qingyixiaoxia 微信号:qingyixiaoxia
在【设计模式启示录 (一)】中,重点介绍了设计模式的精髓(抽象),设计模式的分类(按抽象的目的进行分类)。在本篇中,将按照前述的七大分类,逐一详解常见的设计模式。每个设计模式的阐述按照如下结构组织:
1. 设计图示:
示意图:力求描述出一个模式的精神本质;
标准图:GOF设计模式给出的标准设计;
2. 设计详解
设计模式的设计目标:阐述一个模式要达成的效果。
设计模式的适用场景:阐述一个模式在什么情景下适用。
设计模式的落地方法:阐述一个模式在应用时的关键点。
3. 案例
笔者的GitHub有一个SWDesignPatternSamples工程,里面保护了23种设计模式的代码示例。所有案例均尽力围绕【设计模式启示录 (一)】种提出的物联网问题域。目前23种设计模式的代码示例只完成了一部分,大家感兴趣可以在GitHub上补充。地址:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/
如需转载请注明出处:http://blog.****.net/qingyixiaoxia 微信号:qingyixiaoxia
一.组件接口相关的模式
[--- Adapter适配器模式 ---]
1. 设计图示:
示意图
标准图
2. 设计详解:
1)适配器模式的设计目标:
实现所调用方法库的‘无痕替换’。即业务组件中调用的某方法库,如果需要替换为新库,可以在新库的基础上添加一个适配程序,从而实现不改变业务组件而完成替换。
2)适配器模式的适用场景:
对需要长期维护的程序(十年,二十年),适配器模式的运用变得很重要。设想一个若干年前开发的服务端程序,开发时MFC正值壮年,于是此服务端程序直接依赖了MFC库。未曾想,软件开发的技术日新月异,时至今日MFC早已被拍在沙滩上,此时若想把程序变成跨平台的,就要破费工时去大改特改了。而如果程序开发之初就没有直接依赖MFC库,而是倚赖一个适配器抽象,那么在实现跨平台时,只要追加新的适配器实现就可以了。
3)适配器模式的落地方法:
业务组件中调用某个类型的方法库时,去主动的添加一个适配程序。该适配程序对方法库的接口进行抽象,业务组件依赖此抽象,而不是直接依赖目标方法库。这样后续替换方法库时,只要针对新的方法库添加新的适配程序实现即可。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/InterfaceRelated
[--- Facade外观模式 ---]
1. 设计图示:
示意图
标准图
2. 设计详解:
1)外观模式的设计目标:
为复杂的接口加上一顶简洁的帽子,让复杂接口的调用简单化。
2)外观模式的适用场景:
其一,调用外部库:例如os socket库,os thread库,libuv库,等等;
其二,提供调用库;
3)外观模式的落地方法:
其一,调用外部库时:调用一个接口复杂的库时,可以按需增加一个简单的外观,屏蔽不需要的接口细节和调用过程,从而避免业务代码中处处需要和原生的复杂接口打交道。
其二,提供调用库时:设计一个库的接口时,避免单纯的为了调用方便而牺牲灵活性,而仅仅提供一些简单小白的接口。正确的方法是,库的底层接口应当提供足够的配置上和流程上的灵活性,在此基础上,可以基于这样的底层接口提供一个简单易用版的小白接口。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/InterfaceRelated
[--- Proxy代理模式 ---]
1. 设计图示:
示意图
标准图
2. 设计详解:
1)代理模式的设计目标:
屏蔽对代理对象调用的某类细节,例如网络过程,对象数目控制,对象权限控制,对象测试方法,等等。在被代理对象的基础上,按需叠加需要封装的内容。
2)代理模式的适用场景:
远程代理:调用远程方法时,用代理来封装网络交互过程,从而避免在业务程序中直接描述网络过程。
测试代理:用测试代理来封装测试数据和测试方法,从而避免需要修改业务逻辑内部来完成测试。
其他代理:~
3)代理模式的落地方法:
如果被代理对象有抽象接口,那么代理可以基于此抽象接口实现。
如果被代理对象没有抽象接口,那么最好追加一个抽象接口的定义,并基于此接口完成一个代理的实现和一个非代理的实现(对代理对象的简单调用)。这样可以方便的对代理,非代理,以及其他实现的代理,进行切换。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/InterfaceRelated
如需转载请注明出处:http://blog.****.net/qingyixiaoxia 微信号:qingyixiaoxia
二.组件或模块衔接相关的模式
[--- Bridge桥接模式 ---]
1. 设计图示:
示意图
标准图
2. 设计详解:
1)桥接模式的设计目标:
避免对变化因素的穷举式编程。试想业务组件中有两类互相依赖的变化因素,这两类变化分别有M和N种可能性。那么最糟糕的情况下,我们需要完成M*N个编程CASE来完成对M*N中可能性的覆盖。这显然是很糟糕的。桥接模式的设计目标就是为了避免这种糟糕的编程。
2)桥接模式的适用场景:
组件中有多个彼此依赖的变化因素。例如多业务策略 +多设备类型。再如经典的多系统类型+多界面需求类型(console界面 或者 图形界面)。
3)桥接模式的落地方法:
将两类变化因素分别进行抽象,以某个变化因素抽象为调用方,另外一个变化因素抽象为被调用方,尽量将两类因素间的依赖关系在抽象中描述。当然,如果有的依赖关系实在没办法在抽象中完成,也可以下方到具体的变化因素实现中完成。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/LogicCombineRelated
[--- Mediator中介者模式 ---]
1. 设计图示:
示意图
标准图
2. 设计详解:
1)Mediator模式的设计目标:
用一个专门的衔接模块来处理多个功能模块间的调用关系,从而避免各个功能模块间直接互相调用,由此提升各模块的可复用性和可维护性。
2)Mediator模式的适用场景:
Everywhere ~~~。
3)Mediator模式的落地方法:
开发一个业务组件时,应当抽出若干相对独立的功能模块,并用额外的Mediator模块来衔接各个功能模块。避免功能模块间依赖关系变成一团毛线,复用度低不说,可维护性差不说,一团毛线还可能把自己绕晕了。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/LogicCombineRelated
如需转载请注明出处:http://blog.****.net/qingyixiaoxia 微信号:qingyixiaoxia
三.组件或模块抽取相关的模式
[--- Strategy策略模式 ---]
1. 设计图示:
示意图
标准图
2. 设计详解:
1)策略模式的设计目标:
业务组件依赖算法的抽象接口,当切换算法的实现策略时,仅仅更改具体使用的策略实现类即可。避免业务组件直接依赖算法实现,为了切换策略而到处if else。
2)策略模式的适用场景:
当组件所需的某算法或者业务模块有多种实现方式,使用策略模式可以避免在组件中到处switch case。
3)策略模式的落地方法:
针对多实现方法的算法或者业务模块,对其接口做抽象,调用者依赖抽象。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/LogicExtractRelated
[--- Command命令模式 ---]
1. 设计图示:
示意图 - 1
示意图 - 2
标准图
2. 设计详解:
1)命令模式的设计目标:
让事件的请求和处理具有极大的灵活性。事件的要素包括请求模块和处理模块,command模式使得事件请求模块可以自由选择所需的处理模块,而且使得事件的处理模块具有通用度。如果没有command模式,最糟糕的情况下请求模块和处理模块直接依赖,完全没有灵活性和通用度。
2)命令模式的适用场景:
其一,当业务组件中有多个事件请求方(发出请求处理的流程节点)和多个事件处理方(事件的处理逻辑),而且需要事件请求方和事件处理方能灵活的搭配时,可以考虑用命令模式。
其二,当业务组件中需要将某些逻辑抽象出来,并提供给多处调用时,也可以考虑使用命令模式进行封装。
命令模式最典型的适用场景是界面程序开发中菜单/按钮的响应。
3)命令模式的落地方法:
其一,Command可以视为衔接事件请求模块和事件处理模块。命令的抽象,包括命令提供的接口和参数要考虑清楚,要充分照顾到可能的变化。否则一旦发现命令抽象不合适,改动抽象意味着所有命令Impl也要跟着改动。
其二,command亦可以视为将业务组件中某些逻辑抽取出来包装为命令。这种视角的命令模式使用,要点同样也是命令接口和参数的定义。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/LogicExtractRelated
[--- Visitor访问者模式 ---]
1. 设计图示:
示意图 -1
示意图 -2
标准图
2. 设计详解:
1)访问者模式的设计目标:
Visitor模式将对元素的访问从元素集合的操作中分离出来,使得元素集合中每个元素的访问方法可以按需灵活变化,而且元素的访问方法可以通用于不同的集合。
(Visitor和Iterator像一对兄弟:Visitor将元素的访问方法从集合中分离出来,Iterator将元素的遍历方法从集合中分离出来)
2)访问者模式的适用场景:
其一,处理元素集合时,元素的访问方法需要多样化时;
其二,元素的访问方法需要用于不同元素集合时;
典型的应用场景,如文件的遍历,表树图等的遍历。
3)访问者模式的落地方法:
调用者按需创建特定的visitor实现,将实现赋值给待操作的集合类。注意Visitor的抽象可能要考虑到多样的元素类型,包括int,float,struct,class等。切记,一旦抽象类定义错了,改动的代价是大大的~~
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/LogicExtractRelated
[--- Iterator迭代器模式 ---]
1. 设计图示:
示意图
标准图
2. 设计详解:
1)迭代器模式的设计目标:
Iterator模式将对元素的遍历从元素集合的操作中分离出来,使得集合的遍历方法可以按需变化,而且遍历方法可以通用于不同的集合。
(Iterator和Visitor像一对兄弟:Visitor将元素的访问方法从集合中分离出来,Iterator将元素的遍历方法从集合中分离出来)
2)迭代器模式的适用场景:
最经典的案例当属C++ STL中各种容器的Iterator迭代器。
3)迭代器模式的落地方法:
可以参考C++ STL中Iterator迭代器的实现。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/LogicExtractRelated
如需转载请注明出处:http://blog.****.net/qingyixiaoxia 微信号:qingyixiaoxia
四.模块通用性相关的模式
[--- Template模板方法模式 ---]
1. 设计图示:
示意图
标准图
2. 设计详解:
1)模板方法模式的设计目标:
模板的目的是让算法模块通用于数据类型,也即将数据类型变量化。
变量:将数值变量化;
模板:将数据类型变量化;
Callback:将函数变量化;
2)模板方法模式的适用场景:
当一个方法或者类中某个或者某几个变量,需要通用于不同的数据类型,可以采用模板方法模式。
3)模板方法模式的落地方法:
C++中的template编程。
经典的应用案例如C++ STL中vector,list,map等容器。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/LogicReuseRelated
如需转载请注明出处:http://blog.****.net/qingyixiaoxia 微信号:qingyixiaoxia
五.事件通知相关的模式
[--- Observer观察者模式 ---]
1. 设计图示:
示意图
标准图
2. 设计详解:
1)观察者模式的设计目标:
观察者提供了一个Event Notify事件通知侦听模型。
事件通知在语法上有两种基本机制:callback函数,listener对象。与callback函数相比,listener对象的好处之一在于自然的携带侦听对象的环境信息。
2)观察者模式的适用场景:
Everywhere ~
3)观察者模式的落地方法:
观察者模式在通知事件时有两类方式:
其一,在通知事件中携带数据,各listener收到事件后直接解包和处理数据即可。这种方式的好处在于listener可以直接处理事件数据,弊端在于不同的listener对数据的需求未见的一致,由此可能催生臃肿的事件消息定义。
其二,在通知事件中不携带数据,各listener收到事件后,调用发送方提供的接口来捞取数据。这种方式的好处在于灵活,不同的listener可以按需捞取自己所关心的数据。
特别注意,谨慎的为callback/listener的调用过程加锁,否则一不小心就掉坑儿里。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/NotifyRelated
[--- Responsibility Chain责任链模式 ---]
1.设计图示:
示意图
标准图
2. 设计详解:
1)责任链模式的设计目标:
观察者提供了一个Action Request动作请求侦听模型。
Listener模式一般用于处理事件侦听,其特性在于所有侦听者逐一处理事件数据。
Responsibility Chain模式一般用于处理动作请求侦听,其特性在于有唯一的侦听者适合处理动作请求。
2)责任链模式的适用场景:
Everywhere~
3)责任链模式的落地方法:
责任链模式的动作发送发需要找到一个适合处理当下请求的侦听者。有两种方式:
其一,动作发送方遍历各个侦听者,知道找到适合处理的即停止遍历。
其二,各个侦听者互连为”链”,动作发送方只需要将请求发送给”链头”,请求在”链”上被传递,知道找到适合处理的侦听节点。
特别注意,谨慎的为callback/listener的调用过程加锁,否则一不小心就掉坑儿里。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/NotifyRelated
如需转载请注明出处:http://blog.****.net/qingyixiaoxia 微信号:qingyixiaoxia
六.一些特定的逻辑模型
[--- Composite组合模式 ---]
1. 设计图示:
示意图
标准图
2. 设计详解:
1)组合模式的设计目标:
其一,组合模式提供了基于面向对象聚合/组合的灵活的树状结构定义,使得调用者可以按需自由的描绘自己需要的树的样子。组合模式赋予了面向对象中的组合/聚合关系(VS继承关系)以最大的灵动性。如果没有组合模式,那么对象的组合/聚合关系某种意义上是’硬编码’的,也即要改变组合/聚合的具体构成,就要修改相应的类实现。而组合模式,使得对象的组合/聚合关系可以被外部自由的改变。
其二,组合模式也可以视为类的递归(VS函数递归)。要描述递归的过程或者结构,组合模式为我们提供了用类进行描述的方法。树状结构,是适用组合模式的典型。
2)组合模式的适用场景:
如果对象具有’灵活树结构’,也即对象是树状结构的,而且树的形状是变化而非固定的,此时可以考虑使用组合模式。典型的如,文件系统。
3)组合模式的落地方法:
组合模式中,leaf节点即递归的结束条件,composite节点即递归的拆分方法,而抽象的各个abstract方法,即待递归的过程。组合模式的设计方案其实很简单,关键的部分是做好树结构/递归的抽象。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/LogicModelRelated
[--- Decorator装饰者模式 ---]
1. 设计图示:
示意图
标准图
2. 设计详解:
1)装饰者模式的设计目标:
其一,装饰者模式提供了极为灵活的基于面向对象聚合/组合的功能扩展方法。调用者可以按需自由灵活的进行功能的组合。
其二,装饰者模式在语法层面和组合模式非常像,在语法上可以视为一种特殊的组合模式。然而在概念意义上,装饰者模式和组合模式是截然不同的。组合模式呈现出的意义是一个树状结构。装饰者模式呈现出的意义是层层修饰灵活扩充,不断自内而外丰富其内容。
2)装饰者模式的适用场景:
木用过,不好说。后续补充。
3)装饰者模式的落地方法:
木用过,不好说。后续补充。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/LogicModelRelated
[--- Interpreter解释者模式 ---]
1. 设计图示:
示意图
标准图
2. 设计详解:
1)解释者模式的设计目标:
其一,解释者模式为表达式语言定义而生,为表达式语言的描述提供了灵活易扩充的3.设计方案。
其二,解释者模式的定义和组合模式区别不大,解释模式无论在语法层面还是概念意义层面均可视为一种特殊的组合模式。
2)解释者模式的适用场景:
表达式语言解释处理程序。例如正则表达式,四则运算表达式,等等。
3)解释者模式的落地方法:
和组合模式类似,解释者模式的节点也分terminal节点(leaf节点)和nonterminal节点(composite节点)。terminal节点对应仅仅包含一个符号的表达式,nonterminal节点对应包含多个符号的表达式,而nonterminal节点内部的组合方法则对应了待处理表达式语言的语法规则。
笔者用组合模式处理过简单的四则运算表达式,对C++来说用组合/装饰者/解释者模式,在语法上并不方便。C++的多态依赖虚函数,要达到多态的效果需要用指针进行传递。然而对组合/装饰者/解释者模式,用指针来组合万勿当心释放的问题,一个对象的指针只能复制给一个composite节点,否则有重复释放的危险哦。大家可以写写四则运算的例子感受下。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/LogicModelRelated
[--- State状态模式 ---]
1. 设计图示:
示意图
标准图
2. 设计详解:
1)状态模式的设计目标:
状态模式为状态机的描述而生,提供了灵活的状态机实现方案。
2)状态模式的适用场景:
使用状态机的场合。最典型的如TCP的状态变迁管理。
3)状态模式的落地方法:
状态模式的设计方案其实很简单,无非对状态进行一个简单的抽象。状态机模式的落地,比模式本身更重要的是逻辑上的设计,即状态和状态跃迁的设计。
3. 案例:
https://github.com/qingyixiaoxia/SWDesignPatternSamples/tree/master/LogicModelRelated
如需转载请注明出处:http://blog.****.net/qingyixiaoxia 微信号:qingyixiaoxia
七.对象创建相关的模式
设计模式强调对抽象编程,而避免依赖具体的实现,但是有一类编程避无可避的要针对实现编程,要依赖具体的实现,这就是对象创建。GOF的23设计模式中,对象创建类的有Abstract Factory,Factory Method,Builder,Prototype,Singleton五种设计模式。笔者的经验,绝大部分情景使用简单的Simple Factor来处理对象的创建足够了。对象创建类的模式其实都很简单,大家感兴趣可以查阅相关资料。