设计模式

本文是参考学习《设计模式》的一些笔记和感想;

设计模式之于面向对象系统设计和开发的作用 就如同数据结构至于面向过程 开发;

追求高内聚、低耦合;

原则:面向对象封装、继承、多态、面向接口编程、优先使用组合而不是继承、讲抽象和实现分离的思想等等。

设计模式是一种思想,而思想是指导行为的一切。

所谓设计模式其实就是总结出来的一些可以解决特定问题的通用结构实际是基础知识的组合继承、组合、多态、友元、static、Public、protect、Private保护的应用

对应模式的代码,可自己写。

 

UML 的 常见表示:虚线:组合包含:----->

继承(泛化):空心箭头 + 实线;
组合 ,黑心箭头+ 实线{父类的指针};组合:clleage中维护一个受保护的成员Mediator*;组合:ConcreteMediator 维护一个私有的成员;组合,使用链表 或者数组;

 

1 创建型模式.

多态基类的指针来指向实际的子类实现
实际问题1、n多的子类继承自抽象基类(名字不同),不得不在每次要用到子类的地方就编写诸如new ×××;的代码, 要创建的子类实在是太多;
实际问题2、在父类中并不知道具体要实例化哪一个具体的子类;

1.1 Factory模式主要功能: 
1)定义创建对象的接口,封装了对象的创建; {统一的接口;}
2)使得具体化类的工作延迟到了子类中。{子类中实现实例化;}

代码见《C++设计模式.pdf》-- K_Eckel 


Factory模式的应用并不是只是为了封装对象的创建而是要把对象的创建放到子类中实现Factory中只是提供了对象创建的接口CreateProduct,其实现将放在Factory的子类ConcreteFactory中进行(重写CreateProduct)


好处: 
Factory模式后系统可读性性和维护;

问题:
1、因为为每一个具体的ConcreteProduct类的实例化提供一个函数体
Factory的接口永远就不可能封闭(Close)

2、在实现中我们可以通过参数化工厂方法,
即给FactoryMethod()传递一个参数用以决定是创建具体哪一个具体的Product。

3、局限: Factory模式仅仅局限于一类类(就是说Product是一类,有一个共同的基类)。


如果我们要为不同类的类提供一个对象创建的接口,那就要用AbstractFactory了

 

1.2 AbstactFactory模式

实际问题1:游戏开发中,多种等级怪物,多个关卡,不同的关卡对应不同的等级怪物;
AbstractFactory模式就是用来解决这类问题的:要创建一组相关或者相互依赖的对象。

AbstractFactory模式关键就是将这一组对象的创建 封装 到一个用于创建对象的类(ConcreteFactory)中,维护这样一个创建类总比维护n多相关对象的创建过程要简单的多

评价:这是一个复用的好方法

AbstractFactory模式为创建一组(有多类)相关或依赖的对象提供创建接口,
Factory模式正如我在相应的文档中分析的是为一类对象提供创建接口或延迟对象的创建到子类中实现

 

1.3 Singleton模式. 

单例模式:创建一个唯一的变量(对象);

评价: 实质上是使用static 全局变量和全局指针,维持唯一对象;
是一种用得最平常的模式;

 

1.4 Builder模式 
问题:当我们要创建的对象很复杂的时候(通常是由很多其他的对象组合而成),
将一个 复杂对象的构建 与 它的表示 分离,使得 同样的构建过程 可以创建不同的表示(在示例代码中可以通过传入不同的参数实现这一点;
好处:一步步的进行复杂对象的构建,由于在每一步的构造过程中可以引入不同参数使得经过相同的步骤创建最后得到的对象的展示不一样。

Builder模式AbstractFactory模式功能上很相似,因为都是用来创建大的复杂的对象
区别:Builder模式强调的是一步步创建对象,并通过相同的创建过程可以获得不同的结果对象,一般来说Builder模式中对象不是直接返回的;AbstractFactory模式对象是直接返回的AbstractFactory模式强调的是为创建多个相互依赖的对象提供一个同一的接口。

评价:实际上是使用一个类将初始化的步骤进行汇总的,初始化对象成员必须要是有参构造函数。

Prototype模式
也有叫原型模式 。 
问题:提供了自我复制的功能
Prototype模式提供了一个通过已存在对象进行新对象创建的接口

评价: 实际上就是一个C++的拷贝构造函数; 

实际上Prototype模式和Builder模式、AbstractFactory模式 都是 通过一个类(对象实例)专门负责对象的 创建 工作(工厂对象);
它们之间的区别是:
Builder模式重在复杂对象的一步步创建(并不直接返回对象)
AbstractFactory模式重在产生多个相互依赖类的对象,直接返回对象
Prototype模式重在从自身复制自己创建新类

 

2 结构型模式 

2.1 Bridge模式 
实际问题: 由于客户的需求带来的类的迅速膨胀,N多层继承.;
使用组合(委托)的方式将抽象和实现彻底地解耦,,这样的好处是抽象和实现可以分别独立地变化,系统的耦合性也得到了很好的降低。

评价: bridge模式实际上就是通过对象组合实现用户的需求,而不是继承
    根本区别在于是通过继承还是通过组合的方式去实现一个功能需求。
符合设计原则: Favor Composition Over Inheritance。

2.2 Adapter模式.
 应用场景:使用第三方的库,发现我们设计的接口与第三方提供的接口不一致
,Adapter模式提供了将一个类(第三方库)的接口转化为客户(购买使用者)希望的接口。

类模式的Adapter采用继承的方式复用Adaptee的接口,而在对象模式的Adapter中我们则采用组合的方式实现Adaptee的复用;
评价: 实际上就是在中间加了一层封装,可以使用继承也可以使用组合;

接口继承和实现继承:接口继承指的是通过继承,子类获得了父类的接口;实现继承指的是通过继承子类获得了父类的实现;

2.3 Decorator模式. 
应用场景: 我们需要为一个已经定义好的类(具体子类)添加新的职责(操作);如果是继承,使得深度变得很深很复杂;
当具体子类要添加新的职责,就必须向其父类添加一个这个职责的抽象接口,否则是通过父类指针是调用不到这个方法了。这样处于高层的父类就承载了太多的特征(方法),并且继承自这个父类的所有子类都不可避免继承了父类的这些接口,但是可能这并不是这个具体子类所需要的。

Decorator 继承Component,同时,组合一个指向基类的一个指针,这样,新增的功能在Decorator的子类中实现,这样,子类功能的增加就不用修改基类的接口;
实际上:让Decorator直接拥有一个ConcreteComponent的引用(指针)也可以达到修饰的功能。
终极目标是: 子类单独加功能,不影响父类; 

Decorator模式和Proxy模式的相似的地方在于它们都拥有一个指向其他对象的引用(指针),即通过组合的方式来为对象提供更多操作(或者Decorator模式)间接性(Proxy模式)。
但是他们的区别是,Proxy模式会提供使用其作为代理的对象一样接口,使用代理类将其操作都委托给Proxy直接进行。这里可以简单理解为组合和委托之间的微妙的区别了。

 

2.4 Composite模式. 
应用场景: 递归构建树状的组合结构;

Composite模式是要提供对于子节点(Leaf)的管理策略。

Composite模式通过和Decorator模式有着类似的结构图,区别在于:
Composite模式旨在构造类,而Decorator模式重在不生成子类即可给对象添加职责。Decorator模式重在修饰,而Composite模式重在表示。

2.5 Flyweight模式. 
应用场景:如果一个应用程序使用了太多的对象,就会造成很大的存储开销;。特别是对于大量轻量级(细粒度)的对象;
Flyweight模式在实现过程中主要是要为共享对象提供一个存放的“仓库”(对象池)。

评价: 实际上就是将很多的对象存放在一个容器中;
评价: 实际上就是通过组合两个类的句柄,将不同类的操作集合到一起;

2.6 Facade模式 
实际应用场景: 需要实现的接口在不同的模块之内,客户程序员只想简单的组合你的A-D的类的接口,他并不想知道这些接口在哪里实现的。 


2.7 Proxy模式... 
实际应用场景:工作交给一个代理:
为网络上的对象创建一个局部的本地代理;
创建开销大的对象时候,比如显示一幅大的图片,我们将这个创建的过程交给代理;
对对象进行控制访问的时候,比如在Jive论坛中不同权限的用户;我们将这个工作交给一个代理去完成
p的Request请求实际上是交给了sub来实际执行。
评价: 最大的好处就是实现了逻辑和实现的彻底解耦。

 


3 行为模式 
3.1 Template模式 (继承)
适用场景:
对于某一个业务逻辑(算法实现)在不同的对象中有不同的细节实现,但是逻辑(算法)的框架(或通用的应用算法)是相同的。Template提供了这种情况的一个实现框架。

Template模式是采用继承的方式实现这一点:将逻辑(算法)框架放在抽象基类中,并定义好细节的接口,子类中实现细节。
关键点就是将通用算法封装在抽象基类中,并将不同的算法细节放到子类中实现。

评价:实际上就是继承 实现多态,实现算法实现细节和高层接口的松耦合;
唯一注意的是我们将原语操作(细节算法)定义保护(Protected)成员,只供模板方法调用(子类可以)。
符合原则:DIP(依赖倒置:Dependency Inversion Principles):父类调用子类的操作(高层模块调用低层模块的操作),低层模块实现高层模块声明的接口。这样控制权在父类(高层模块),低层模块反而要依赖高层模块。

不足:我们可以看到对于ConcreteClass类中的实现的原语方法Primitive1(),是不能被别的类复用。

【注释1】:Strategy模式解决的是和Template模式类似的问题,但是Strategy模式是将逻辑(算法)封装到一个类中,并采取组合(委托)的方式解决这个问题。

3.2 Strategy模式. (委托)
通过组合的方式将具体算法的实现在组合对象中实现,再通过委托的方式将抽象接口的实现委托给组合对象实现;
关键就是将算法的逻辑抽象接口(DoAction)封装到一个类中(Context),再
通过委托的方式将具体的算法实现委托给具体的Strategy类来实现(ConcreteStrategeA 
类)。

trategy模式和Template模式实际是实现一个抽象接口的两种方式:继承和组合之间的区别。要实现一个抽象接口,继承是一种方式:我们将抽象接口声明在基类中,将具体的实现放在具体子类中。组合(委托)是另外一种方式:我们将接口的实现放在被组合对象中,将抽象接口放在组合类中。

面向对象的一条规则: 优先使用(对象)组合,而非(类)继承(Favor Composition Over Inheritance)。

继承: 
?? 优点 
1)易于修改和扩展那些被复用的实现。
?? 缺点 
1)破坏了封装性,继承中父类的实现细节暴露给子类了; 
2)“白盒”复用,原因在1)中; 
3)当父类的实现更改时,其所有子类将不得不随之改变
4)从父类继承而来的实现在运行期间不能改变(编译期间就已经确定了)。 

组合 
?? 优点 
1)“黑盒”复用,因为被包含对象的内部细节对外是不可见的; 2)封装性好,原因为1); 
3)实现和抽象的依赖性很小(组合对象和被组合对象之间的依赖性小); 
4)可以在运行期间动态定义实现(通过一个指向相同类型的指针,典型的是抽象基类的指针)。
?? 缺点 
1)系统中对象过多。
 

3.3 State模式 
每个人、事物在不同的状态下会有不同表现(动作),而一个状态又会在不同的表现下 
转移到下一个不同的状态(State)
问题:
1)当状态数目不是很多的时候,Switch/Case可能可以搞定。但是当状态数目很多的时候(实际系统中也正是如此),维护一大组的Switch/Case语句将是一件异常困难并且容易出错的事情。
2)状态逻辑和动作实现没有分离。在很多的系统实现中,动作的实现代码直接写在状态的逻辑当中。这带来的后果就是系统的扩展性和维护得不到保证。

两者之间的区别就是State模式中具体实现类中有一个指向Context的引用,而Strategy模式则没有。
State模式在实现中,有两个关键点: 
1)    将State声明为Context的友元类(friend class),其作用是让State模式访问Context的protected接口ChangeSate()。
2)    State及其子类中的操作都将Context*传入作为参数,其主要目的是State类可以通过这个指针调用Context中的方法(在本示例代码中没有体现)。这也是State模式和Strategy模式的最大区别所在。

Strategy模式很State模式也有相似之处,但是State模式注重的对象在不同的状态下不同的操作
State模式很好地实现了对象的状态逻辑和动作实现的分离,状态逻辑分布在State的派生类中实现,而动作实现则可以放在Context类中实现(这也是为什么State派生类需要拥有一个指向Context的指针);

缺点:State模式问题主要是逻辑分散化,状态逻辑分布到了很多的State的子类中,很难看到整个的状态逻辑图,这也带来了代码的维护问题。

3.4 Observer模式.(MVC).. 
其实例为Model-Control-View模式,是MFC和Struts中的基本框架。
实现了业务逻辑和表示层的解耦。个人也认为Observer模式是软件开发过程中必须要掌握和使用的模式之一。
最常见的一个例子就是:对同一组数据进行统计分析时候,我们希望能够提供多种形式的表示(例如以表格进行统计显示、柱状图统计显示、百分比统计显示等)。这些表示都依赖于同一组数据,我们当然需要当数据改变的时候,所有的统计的显示都能够同时改变。Observer模式就是解决了这一个问题。
MVC只是Observer模式的一个实例。Observer模式要解决的问题为:建立一个一(Subject)对多(Observer)的依赖关系,并且做到当“一”变化的时候,依赖这个“一”的多也能够同步改变。
这里的
目标Subject提供依赖于它的观察者Observer的注册(Attach)和注销(Detach)操作(注册和注销起始就是对链表List成员的添加和删除成员),并且提供了使得依赖于它的所有观察者同步的操作(Notify)。
观察者Observer则提供一个Update操作,注意Observer的Update操作并不在Observer改变了Subject目标状态的时候就对自己进行更新,这个更新操作要延迟到Subject对象发出Notify通知所有Observer进行修改(调用Update)。

在Observer模式的实现中,Subject维护一个list作为存储其所有观察者的容器。每当
调用Notify操作就遍历list中的Observer对象,并广播通知改变状态(调用Observer的Update操作)。
目标的状态state可以由Subject自己改变(示例),也可以由Observer的某个操作引起state的改变(可调用Subject的SetState操作)。Notify操作可以由Subject目标主动广播(示例),也可以由Observer观察者来调用(因为Observer维护一个指向Subject的指针,这个是关键)。


评价: Observer 是观察者,是VIEW,Subject 是Control的部分; 


3.5 Memento模式..(撤销操作)
 应用场景:关键性的操作肯定需要提供诸如撤销(Undo)的操作。用户要求的权利:

原理:Memento模式的关键就是要在不破坏封装行的前提下,捕获并保存一个类的内部状态,这样就可以利用该保存的状态实施恢复操作。
Memento模式的关键就是friend class Originator (
//这是最关键的地方,将Originator为friend类,可以访问内部信息,但是其他类不能访问)
Memento的接口都声明为private,而将Originator声明为Memento的友元类。我们将Originator的状态保存在Memento类中,而将Memento接口private起来,也就达到了封装的功效

3.6 Mediator模式.(通讯解耦)
对象间的交互本身就是一种通信;
应用场景:提供一个专门处理对象间交互和通信的类,用于系统规模大复杂的通信;

。Mediator模式提供将对象间的交互和通讯封装在一个类中,各个对象间的通信不必显势去声明和引用,大大降低了系统的复杂性能,(了解一个对象总比深入熟悉n个对象要好);
实现特点:Mediator模式中,每个Colleague维护一个Mediator,当要进行交互,例如图中ConcreteColleagueA和ConcreteColleagueB之间的交互就可以通过ConcreteMediator提供的DoActionFromAtoB来处理,ConcreteColleagueA和ConcreteColleagueB不必维护对各自的引用,甚至它们也不知道各个的存在。Mediator通过这种方式将多对多的通信简化为了一(Mediator)对多(Colleague)的通信。

将对象Colleague之间的通信封装到一个类种单独处理;将多对多的通信转化为一对多的通信,降低了系统的复杂;
通过Mediator,各个Colleague就不必维护各自通信的对象和通信协议,降低了系统的耦合性,Mediator和各个Colleague就可以相互独立地修改了;

Mediator模式还有一个很显著额特点就是将控制集中,集中的优点就是便于管理,也正式符合了OO设计中的每个类的职责要单一和集中的原则。


3.7 Command模式 (请求封装)
Command模式通过将请求封装到一个对象(Command)中,并将请求的接受者存放到具体的ConcreteCommand类中(Receiver)中,从而实现调用操作的对象和操作的具体实现者之间的解耦。

Command模式结构图中,将请求的接收者(处理者)放到Command的具体子类ConcreteCommand中,当请求到来时(Invoker发出Invoke消息激活Command对象),ConcreteCommand将处理请求交给Receiver对象进行处理。

其关键就是将一个请求封装到一个类中(Command),再提供处理对象(Receiver),最后Command命令由Invoker激活;

另外,我们可以将请求接收者的处理抽象出来作为参数传给Command对象,实际也就是回调的机制(Callback)来实现这一点,也就是说将处理操作方法地址(在对象内部)通过参数传递给Command对象,Command对象在适当的时候(Invoke激活的时候)再调用该函数。

Command模式关键就是提供一个抽象的Command类,并将执行操作封装到Command类接口中,Command类中一般就是只是一些接口的集合,并不包含任何的数据属性

3.8 Visitor模式..(满足需求的变化)
应用场景:需求变更,咱们的客户又会有了新的需求。
Visitor模式则提供了一种解决方案:将更新(变更)封装到一个类中(访问操作),并由待更改类提供一个接收接口,则可达到效果。

Visitor模式的关键是双分派(Double-Dispatch)的技术【注释1】。C++语言支持的是单分派。

具体调用哪一个具体的Accept()操作,有两个决定因素:1)Element的类型。因为Accept()是多态的操作,需要具体的Element类型的子类才可以决定到底调用哪一个Accept()实现;2)Visitor的类型。Accept()操作有一个参数(Visitor* vis),要决定了实际传进来的Visitor的实际类别才可以决定具体是调用哪个VisitConcrete()实现。
两个关键都是用多态来实现;

代码特点(两种实现方式):
1、
Element类仅仅提供一个接口Visit(),而在Accept()实现中具体调用哪一个Visit()操作则通过函数重载(overload)的方式实现:我们提供Visit()的两个重载版本a)Visit(ConcreteElementA* elmA),b)Visit(ConcreteElementB* elmB)。


2、在C++中我们还可以通过RTTI(运行时类型识别:Runtime type identification)来实现,即我们只提供一个Visit()函数体,传入的参数为Element*型别参数,然后用RTTI决定具体是哪一类的ConcreteElement参数,

缺点:
1、破坏了封装性:
Visitor模式要求Visitor可以从外部修改Element对象的状态,这一般通过两个方式来实现

2、ConcreteElement的扩展很困难:
每增加一个Element的子类,就要修改Visitor的接口,使得可以提供给这个新增加的子类的访问机制。


3.9 Chain of Responsibility模式..(递归访问).. 
应用场景:一类问题将可能处理一个请求的对象链接成一个链,并将请求在这个链上传递,直到有对象处理该请求(可能需要提供一个默认处理所有请求的类,例如MFC中的CwinApp类)。
关键:ConcreteHandleA的对象和h1拥有一个后继ConcreteHandleB的对象h2;
关键是:基类有一个指向本类型的指针成员,这样继承于这个父类的不同的子类对象可以串成一个链表, 然后访问的时候可以依次递归访问;


优势:Chain of Responsibility模式的最大的一个有点就是给系统降低了耦合性,请求的发送者完全不必知道该请求会被哪个应答对象处理,极大地降低了系统的耦合性。

3.10 Iterator模式..(容器,迭代期)

Iterator模式则在C++的STL中有实现等
Iterator模式也正是用来解决对一个聚合对象的遍历问题,将对聚合的遍历封装到一个类中进行,这样就避免了暴露这个聚合对象的内部表示的可能。

Iterator模式的实现代码很简单,实际上为了更好地保护Aggregate的状态,我们可以尽量减小Aggregate的public接口,而通过将Iterator对象声明位Aggregate的友元来给予Iterator一些特权,获得访问Aggregate私有数据和方法的机会。


评价:实际上相当于是构造一个容器,使用iterator 指针来访问成员;


3.11 Interpreter模式.. 
解释器为用户提供一个一门定义语言的语法表示的解释器,然后通过这个解释器来解释语言中的句子。
实例: XML的解析器;
XML格式的数据解析是一个在应用开发中很常见并且有时候是很难处理的事情,虽然目前很多的开发平台、语言都提供了对XML格式数据的解析,但是例如到了移动终端设备上,由于处理速度、计算能力、存储容量的原因解析XML格式的数据却是很复杂的一件事情,最近也提出了很多的移动设备的XML格式解析器。但是总体上在项目开发时候还是需要自己去设计和实现这一个过程。
Interpreter模式则提供了一种很好的组织和设计这种解析器的架构。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值