设计模式综述.docx
《设计模式综述.docx》由会员分享,可在线阅读,更多相关《设计模式综述.docx(36页珍藏版)》请在冰豆网上搜索。
设计模式综述
设计模式综述
第一:
创建型模式(全是对象创建型模式)。
1.1抽象工厂——把相关零件组合成产品。
AbstractFactory模式是用来解决这类问题的:
要创建一组相关或者相互依赖的对象。
UML图如下,AbstractFactory模式关键就是将这一组对象的创建封装到一个用于创建对象的类(ConcreteFactory)中,维护这样一个创建类总比维护n多相关对象的创建过程要简单的多。
AbstractFactory模式和Factory模式的区别:
AbstractFactory模式是为创建一组(有多类)相关或依赖的对象提供创建接口,而Factory模式是为一类对象提供创建接口或延迟对象的创建到子类中实现。
并且可以看到,AbstractFactory模式通常都是使用Factory模式实现(ProductFactory1),当然也可以用原型实现。
一个具体的工厂通常是一个单件。
意图:
提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
客户仅适用AbstractFactory和AbstractProduct类声明的接口。
1.2生成器——组合复杂的对象实例。
Builder模式要解决的是这样的问题:
当我们要创建的对象很复杂的时候(通常是由很多其他的对象组合而成),我们要将复杂对象的创建过程和这个对象的表示(展示)分离开来,这样做的好处就是通过一步步的进行复杂对象的构建,由于在每一步的构造过程中可以引入参数,使得经过相同的步骤创建最后得到的对象的展示不一样。
UML图如下所示:
Builder模式的关键是其中的Director对象并不直接返回对象,而是通过一步步(BuildPartA,BuildPartB,BuildPartC)来一步步进行对象的创建。
当然这里Director可以提供一个默认的返回对象的接口(即返回通用的复杂对象的创建,即不指定或者特定唯一指定BuildPart中的参数)。
意图:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示(可以通过传入不同的参数实现这一点)。
Builder模式和AbstractFactory模式在功能上很相似,因为都是用来创建大的复杂的对象,它们的区别是:
Builder模式强调的是一步步创建对象,并通过相同的创建过程可以获得不同的结果对象,一般来说Builder模式中对象是在最后一步返回的。
而在AbstractFactory模式中对象是直接返回的,AbstractFactory模式强调的是为创建多个相互依赖的对象提供一个同一的接口(着重于多个系列的产品对象(简单的和复杂的))。
相关模式:
组成模式通常是用Builder模式生成的。
1.3工厂方法(简单工厂)——建立对象实例交给子类(万事交给子类)。
Factory模式的两个最重要的功能:
1)定义创建对象的接口,封装了对象的创建(工厂方法);
2)使得具体化类的工作延迟到了子类中。
上面两种情况的UML图分别如图1和图2,图2中Factory模式的应用并不是只是为了封装对象的创建,而是要把对象的创建放到子类中实现:
Factory中只是提供了对象创建的接口,其实现将放在Factory的子类ConcreteFactory中进行。
这是图2和图1的区别所在。
工厂方法的一个潜在的缺点在于客户可能仅仅为了创建一个特定的ConcreteProduct对象,就不得不创建Factory的子类。
图1
图2
Factory模式也带来至少以下两个问题:
1)如果为每一个具体的ConcreteProduct类的实例化提供一个函数体,那么我们可能不得不在系统中添加了一个方法来处理这个新建的ConcreteProduct,这样Factory的接口永远就不能封闭(Close)。
当然我们可以通过创建一个Factory的子类来通过多态实现这一点,但是这也是以新建一个类作为代价的。
2)在实现中我们可以通过参数化工厂方法,即给FactoryMethod()传递一个参数用以
决定是创建具体哪一个具体的Product。
当然也可以通过模板化避免1)中的子类创建子类,其方法就是将具体Product类作为模板参数。
可以看出,Factory模式对于对象的创建给予开发人员提供了很好的实现策略,但是Factory模式仅仅局限于一类类(就是说Product是一类,有一个共同的基类),如果我们要为不同类的类提供一个对象创建的接口,那就要用AbstractFactory了。
意图:
定义一个用于创建对象的接口,让子类决定实例化哪一个类。
FactoryMethod使一个类的实例化延迟到其子类。
相关模式:
(1)工厂方法通常在模板方法中被调用;
(2)原型不需要创建Factory子类。
但是,它们要求一个针对Product类的初始化操作。
Factory使用初始化来初始化对象,而FactoryMethod不需要这样做。
1.4原型——复制建立对象实例。
Prototype模式是提供了自我复制的功能,就是说新对象的创建可以通过已有对象进行创建(复制已有对象(浅拷贝和深拷贝))。
UML图为:
Prototype模式提供了一个通过已存在对象进行新对象创建的接口(Clone),Clone()实现和具体的实现语言相关,在C++中我们将通过拷贝构造函数实现的。
Prototype模式通过复制原型(Prototype)而获得新对象创建的功能,这里Prototype本身就是“对象工厂”(因为能够生产对象),实际上Prototype模式和Builder模式、AbstractFactory模式都是通过一个类(对象实例)来专门负责对象的创建工作(工厂对象),它们之间的区别是:
Builder模式重在复杂对象的一步步创建(并不直接返回对象),AbstractFactory模式重在产生多个相互依赖类的对象,而Prototype模式重在从自身复制自己创建新类。
Prototype的主要缺陷是每一个Prototype的子类都必须实现clone操作和拷贝操作函数(默认也可)。
相关模式:
(1)抽象工厂可以存储一个被克隆的原型集合,并且返回产品对象;
(2)大量使用组成和装饰的设计通常也可以从原型模式处获益。
1.5单例——唯一的对象实例。
Singleton模式解决问题十分常见,我们怎样去创建一个唯一的变量(对象)?
在基于对象的设计中我们可以通过创建一个全局变量(对象)来实现,在面向对象和面向过程结合的设计范式(如C++中)中,我们也还是可以通过一个全局变量实现这一点。
但是当我们遇到了纯粹的面向对象范式中,这一点可能就只能是通过Singleton模式来实现了。
UML图为:
在Singleton模式的结构图中可以看到,我们通过维护一个static的成员变量来记录这
个唯一的对象实例。
通过提供一个staitc的接口instance来获得这个唯一的实例。
Singleton不可以被实例化,因此我们将其构造函数声明为protected或者直接声明为private。
Singleton模式经常和Factory(AbstractFactory)模式在一起使用,因为系统中工厂对象一般来说只要一个
第二:
结构型模式(除了2.1全是对象结构型模式)。
2.1适配器——换个包装再度利用(匹配接口)。
(类结构型模式)
在软件系统设计和开发中,这种问题也会经常遇到:
我们为了完成某项工作购买了一个第三方的库来加快开发。
这就带来了一个问题:
我们在应用程序中已经设计好了接口,与这个第三方提供的接口不一致,为了使得这些接口不兼容的类(不能在一起工作)可以在一起工作了,Adapter模式提供了将一个类(第三方库)的接口转化为客户(购买使用者)希望的接口。
Adapter模式的两种类别:
类模式和对象模式。
UML图为:
在Adapter模式的结构图中可以看到,类模式的Adapter采用继承的方式复用Adaptee的接口,而在对象模式的Adapter中我们则采用组合的方式实现Adaptee的复用。
Adapter模式实现上比较简单,要说明的是在类模式Adapter中,我们通过private继承Adaptee获得实现继承的效果,而通过public继承Target获得接口继承的效果。
在Adapter模式的两种模式中,有一个很重要的概念就是接口继承和实现继承的区别和联系。
接口继承和实现继承是面向对象领域的两个重要的概念,接口继承指的是通过继承,子类获得了父类的接口,而实现继承指的是通过继承子类获得了父类的实现(并不统共接口)。
在C++中的public继承既是接口继承又是实现继承,因为子类在继承了父类后既可以对外提供父类中的接口操作,又可以获得父类的接口实现。
当然我们可以通过一定的方式和技术模拟单独的接口继承和实现继承,例如我们可以通过private继承获得实现继承的效果(private继承后,父类中的接口都变为private,当然只能是实现继承了。
),通过纯抽象基类模拟接口继承的效果,但是在C++中purevirtualfunction也可以提供默认实现,因此这是不纯正的接口继承,但是在Java中我们可以interface来获得真正的接口继承了。
意图:
将一个类的接口转换成客户希望的另一个接口。
Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
相关模式:
(1)桥接模式的结构与Adapter模式模式类似,但是桥接模式的出发点不同:
桥接模式的目的是将接口部分和实现部分分离,从而对他们可以较为容易也相对独立的加以改变。
而Adapter模式意味着改变一个已有对象的接口;
(2)装饰模式增强了其他对象的功能同时不改变它的接口。
因此装饰模式对应用程序的透明性比适配器要好。
结果是装饰模式支持递归组合,而纯碎使用适配器是不可能实现这一点的;(3)代理模式在不改变它的接口的条件下,为另一个对象定义了一个代理。
2.2桥接——分成功能层次和实现层次。
意图:
将抽象部分与它的实现部分分离,使他们都可以独立变化(这里的实现不是指的继承基类、实现基类接口,而是指的是通过对象组合实现用户的需求)。
UML图为:
在Bridge模式的结构图中可以看到,系统被分为两个相对独立的部分,左边是抽象部
分,右边是实现部分,这两个部分可以互相独立地进行修改:
例如如果客户需求变化(操作系统变化),当用户需求需要从Abstraction派生一个具体子类时候,并不需要通过继承方式实现时候需要添加子类A1和A2了。
另外当问题中由于算法添加也只用改变右边实现(添加一个具体化子类),而左边不用在变化,也不用添加具体子类了。
Bridge是设计模式中比较复杂和难理解的模式之一,也是OO开发与设计中经常会用到的模式之一。
使用组合(委托)的方式将抽象和实现彻底地解耦,这样的好处是抽象和实现可以分别独立地变化,系统的耦合性也得到了很好的降低。
实际上上面使用Bridge模式和使用带来问题方式的解决方案的根本区别在于是通过继承还是通过组合的方式去实现一个功能需求。
因此面向对象分析和设计中有一个原则就是:
FavorCompositionOverInheritance。
其原因也正在这里。
相关模式:
(1)抽象工厂模式可以用来创建和配置一个特定的桥接模式;
(2)适配器模式用来帮助无关的类协同工作,它通常在系统设计完成后才会被使用。
然而,桥接模式则是在系统开始时就被使用,它使得抽象接口和实现部分可以独立进行改变。
2.3组成(组合)——对容器和内容一视同仁。
在开发中,我们经常可能要递归构建树状的组合结构,Composite模式则提供了很好的解决方案。
将个对象组合成树状结构以表示“部分-整体”的层次结构。
Composite模式使得用户对单个对象和组合对象的使用具有一致性(客户代码中,任何用到基本对象的地方都可以使用组合对象)。
UML图为(从图中可以看出父类和子类的接口相同,所以子类对象可以递归的组合其他子类对象。
):
Composite模式通过和Decorator模式有着类似的结构图,但是Composite模式旨在构造类,而Decorator模式重在不生成子类即可给对象添加职责。
Decorator模式重在修饰,而Composite模式重在表示。
相关模式:
(1)通常部件-父部件连接用于责任链模式;
(2)装饰模式经常和组成模式一起使用。
当装饰和组成一起使用时,他们通常有一个公共的父类。
因此装饰必须支持Add,Remove和GetChild操作的Component接口;(3)享元让你共享组件,但不能再引用他们的部件;(4)迭代器模式可用来遍历组合模式;(5)访问者模式将本来应该分布在Composite和Leaf类中的操作和行为局部化。
2.4装饰——对装饰和内容一视同仁。
在OO设计和开发过程,可能会经常遇到以下的情况:
我们需要为一个已经定义好的类添加新的职责(操作),通常的情况我们会给定义一个新类继承自定义好的类,这样会带来一个问题。
通过继承的方式解决这样的情况还带来了系统的复杂性,因为继承的深度会变得很深。
而Decorator提供了一种给类增加职责的方法,不是通过继承实现的,而是通过组合。
UML图为:
在结构图中,ConcreteComponent和Decorator需要有同样的接口,因此ConcreteComponent和Decorator有着一个共同的父类。
这里有人会问,让Decorator直接维护一个指向ConcreteComponent引用(指针)不就可以达到同样的效果,答案是肯定并且是否定的。
肯定的是你可以通过这种方式实现,否定的是你不要用这种方式实现,因为通过这种方式你就只能为这个特定的ConcreteComponent提供修饰操作了,当有了一个新的ConcreteComponent你又要去新建一个Decorator来实现。
但是通过结构图中的ConcreteComponent和Decorator有一个公共基类,就可以利用OO中多态的思想来实现只要是Component型别的对象都可以提供修饰操作的类,这种情况下你就算新建了100个Component型别的类ConcreteComponent,也都可以由Decorator一个类搞定。
这也正是Decorator模式的关键和威力所在了。
当然如果你只用给Component型别类添加一种修饰,则Decorator这个基类就不是很必要了。
Decorator模式和Proxy模式的相似的地方在于它们都拥有一个指向其他对象的引用(指针),即通过组合的方式来为对象提供更多操作(或者Decorator模式)间接性(Proxy模式)。
但是他们的区别是,Proxy模式会提供使用其作为代理的对象一样接口,使用代理类将其操作都委托给Proxy直接进行。
这里可以简单理解为组合和委托之间的微妙的区别了。
Decorator模式除了采用组合的方式取得了比采用继承方式更好的效果,Decorator模式还给设计带来一种“即用即付”的方式来添加职责。
在OO设计和分析经常有这样一种情况:
为了多态,通过父类指针指向其具体子类,但是这就带来另外一个问题,当具体子类要添加新的职责,就必须向其父类添加一个这个职责的抽象接口,否则是通过父类指针是调用不到这个方法了。
这样处于高层的父类就承载了太多的特征(方法),并且继承自这个父类的所有子类都不可避免继承了父类的这些接口,但是可能这并不是这个具体子类所需要的。
而在Decorator模式提供了一种较好的解决方法,当需要添加一个操作的时候就可以通过Decorator模式来解决,你可以一步步添加新的职责。
注意:
(1)当你仅需要一个职责时,没有必要定义抽象Decorator类;
(2)当Component类原本很庞大时,使用Decorator模式模式代价太高,策略模式相对更好一些;(3)我们可以将一个装饰嵌套在另一个装饰中就可以增加两个装饰。
Decorator模式和策略模式的区别:
Decorator模式仅从外部改变组件,因此组件无需对它的装饰有任何了解;也就是说,这些装饰对该组件是透明的。
在策略模式中,Component组件本身知道可能进行哪些扩充,因此它必须引用并维护相应的策略。
基于策略的方法可能需要修改Component组件以适应新的扩充。
另一方面,一个策略可以有自己特定的接口,而装饰的接口则必须与组件的接口一致。
意图:
动态的给一个对象添加一些额外的职责,就增加功能来说,Decorator模式相比生成子类更为灵活。
相关模式:
(1)Decorator模式改变对象职责而不改变接口,适配器给一个全新的接口;
(2)装饰可以看作仅有一个组件的组合,装饰仅给对象添加职责,它的目的不在于对象聚集;(3)用装饰改变对象的外表,而策略使得你可以改变对象的内核。
2.5外观——单一窗口。
在软件系统开发中经常会遇到这样的情况,可能你实现了一些接口(模块),而这些接口(模块)都分布在几个类中(比如A和B、C、D):
A中实现了一些接口,B中实现一些接口(或者A代表一个独立模块,B、C、D代表另一些独立模块)。
然后你的客户程序员(使用你设计的开发人员)只有很少的要知道你的不同接口到底是在哪个类中实现的,绝大多数只是想简单的组合你的A-D的类的接口,他并不想知道这些接口在哪里实现的。
在现实生活中我们可能可以很快想到找一个人代理所有的事情就可以解决你的问题(你只要维护和他的简单的一个接口而已了!
),在软件系统设计开发中我们可以通过一个叫做Façade的模式来解决上面的问题。
UML图为:
Façade模式在高层提供了一个统一的接口,解耦了系统。
设计模式中还有另一种模式Mediator也和Façade有类似的地方。
但是Mediator主要目的是对象间的访问的解耦(通讯时候的协议)。
Mediator也和Façade类似的地方是:
它抽象了一些已有的类的功能。
然而,中介者的目的是对同事之间的任意通讯进行抽象,通常集中不属于单个对象的功能。
中介者的同事对象知道中介者并与它通信,而不是直接与其他同类对象通信。
相对而言,外观模式仅对子系统对象的接口进行抽象,从而使它们更容易使用;它并不定义新功能,子系统也不知道外观的存在。
意图:
为子系统中的一组接口提供一个一致的界面,Façade模式定义了一个高层接口,这个接口使这一子系统更加容易使用。
相关模式:
通常来讲,仅需要一个外观对象,因此外观对象通常属于单件模式。
注意:
你可能认为外观是另外一组对象的适配器。
但这种解释忽略了一个事实:
即外观定义一个新的接口,而适配器则服用一个原有的接口。
记住,适配器使两个已有的接口协同工作,而不是定义一个全新的接口。
2.6享元——有相同的部分就共享,采取精简政策。
在面向对象系统的设计和实现中,创建对象是最为常见的操作。
这里面就有一个问题:
如果一个应用程序使用了太多的对象,就会造成很大的存储开销。
特别是对于大量轻量级(细粒度)的对象,比如在文档编辑器的设计过程中,我们如果为每个字母创建一个对象的话,系统可能会因为大量的对象而造成存储开销的浪费。
例如一个字母“a”在文档中出现了100000次,而实际上我们可以让这一万个字母“a”共享一个对象,当然因为在不同的位置可能字母“a”有不同的显示效果(例如字体和大小等设置不同),在这种情况我们可以为将对象的状态分为“外部状态”和“内部状态”,将可以被共享(不会变化)的状态作为内部状态存储在对象中,而外部对象(例如上面提到的字体、大小等)我们可以在适当的时候将外部对象最为参数传递给对象(例如在显示的时候,将字体、大小等信息传递给对象)。
Flyweight模式解决上面的问题。
UML图为:
可以从图中看出,Flyweight模式中有一个类似Factory模式的对象构造工厂FlyweightFactory,当客户程序员(Client)需要一个对象时候就会向FlyweightFactory发出请求对象的消息GetFlyweight()消息,FlyweightFactory拥有一个管理、存储对象的“仓库”(或者叫对象池,vector实现),GetFlyweight()消息会遍历对象池中的对象,如果已经存在则直接返回给Client,否则创建一个新的对象返回给Client。
当然可能也有不想被共享的对象(例如结构图中的UnshareConcreteFlyweight),这种对象让用户直接实例化。
Flyweight模式在实现过程中主要是要为共享对象提供一个存放的“仓库”(对象池)。
我们在State模式和Strategy模式中会产生很多的对象,因此我们可以通过Flyweight模式来解决这个问题。
意图:
运用共享技术有效地支持大量细粒度的对象。
注意:
用户不应该直接对ConcreteFlyweight类进行实例化,而只能从FlyweightFactory对象得到ConcreteFlyweight,这可以保证它们适当地进行共享。
在Flyweight中对象之间的共享状态作为内部状态而变换状态作为外部状态(外部状态通过传递参数来特化)。
相关模式:
(1)享元模式通常和组成模式结合起来,用共享的有向无环图实现一个逻辑上的层次结构;
(2)通常,最好用享元实现状态和策略对象。
2.7代理——要用再建立。
至少在以下集中情况下可以用Proxy模式解决问题:
1)创建开销大的对象时候,比如显示一幅大的图片,我们将这个创建的过程交给代理去完成,GoF称之为虚代理(VirtualProxy);
2)为网络上的对象创建一个局部的本地代理,比如要操作一个网络上的一个对象(网络性能不好的时候,问题尤其突出),我们将这个操纵的过程交给一个代理去完成,GoF称之为远程代理(RemoteProxy);
3)对对象进行控制访问的时候,比如在Jive论坛中不同权限的用户(如管理员、普通用户等)将获得不同层次的操作权限,我们将这个工作交给一个代理去完成,GoF称之为保护代理(ProtectionProxy)。
4)智能指针(SmartPointer)。
代理模式的UML图为:
Proxy模式最大的好处就是实现了逻辑和实现的彻底解耦.
意图:
为其他对象提供一种代理以控制对这个对象的访问。
相关模式:
(1)适配器为它适配的对象提供了一个不同的接口,代理提供了与他的实体相同的接口(由于访问级别的问题可能只是实体接口的一个子集);
(2)代理和装饰模式实现相似,但目的不一样,装饰为对象添加职责而代理则控制对对象的访问。
注意:
保护代理和装饰模式实现差不多;远程代理不包含对实体的直接引用,而只是一个间接引用;虚代理开始的时候使用一个间接引用,但最终将获得并使用一个直接引用。
在代理模式中,实体提供了关键的功能,而代理提供(或拒绝)对它的访问。
第三:
行为模式。
3.1职责链——责任传送(对象行为型模式)。
MFC提供了消息的处理的链式处理策略,处理消息的请求将沿着预先定义好的路径依
次进行处理。
消息的发送者并不知道该消息最后是由那个具体对象处理的,当然它也无须也不想知道,但是结构是该消息被某个对象处理了,或者一直到一个终极的对象进行处理了。
ChainofResponsibility模式描述其实就是这样一类问题将可能处理一个请求的对象链接成一个链,并将请求在这个链上传递,直到有对象处理该请求(可能需要提供一个默认处理所有请求的类,例如MFC中的CwinApp类)。
UML图为:
ChainofResponsibility模式中ConcreteHandler将自己的后继对象(向下传递消息的对象)记录在自己的后继表中,当一个请求到来时,ConcreteHandler会先检查看自己有没有匹配的处理程序,如果有就自己处理,否则传递给它的后继。
ChainofResponsibility模式的最大的一个特点就是给系统降低了耦合性,请求的发送者完全不必知道该请求会被哪个应答对象处理,极大地降低了系统的耦合性。