设计模式整理总结(二):结构型模式

二、结构型模式

  结构型模式主要包括:适配器(Adapter)、桥接(Bridge)、组合(Composite)、装饰(Decorator)、外观(Facade)、享元(Flyweight)、代理(Proxy),总共有七种类型。

2.1 适配器(Adapter)模式

  定义:将一个类的接口转换成客户希望的另外一个接口,Adaptor模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
  类适配器模式
adaptor
  对象适配模式
adaptor2
  当需要的东西就在眼前,系统的数据和行为都正确,但是接口不符合,可以使用适配器模式,目的使得原有控制范围之外的一个原有对象与某个接口匹配。适配器模式主要应用于希望使用一些现存的类/工具,但是接口又与复用环境要求不一致的情况。其实现包括类适配器模式、对象适配模式两种:类适配器使用多重继承对一个接口与另一个借口进行匹配;对象匹配器依赖于对象组合的方式。
  Target抽象出客户特定领域所期望的接口;Adaptee定义一个已存在的接口,是需要适配的接口;Adapter对前面的Adaptee接口与Target接口进行适配,如果是类适配ß方式则Adaptor派生Adaptee并实现其中的特定接口,如果是对象适配方式,则通过在内部封装一个私有的Adaptee对象,然后把源接口的调用转换为目标接口。
  举例:一个现成的例子就是STL容器中,有标准的deque双端队列,而要实现stack/queue的数据结构就是通过适配器模式来实现的。

2.2 桥接(Bridge)模式

  定义:将抽象部分与它的实现部分分离,使他们都可以独立地变化。
bridge
  当一个抽象可能有多个实现时候,通常采用继承来协调他们,但是这种方法不够灵活,类的继承关系是在编译的时候就定义好了,子类的实现和它的父类有非常紧密的依赖关系,很难让抽象部分和实现部分独立地进行修改和重用。实现系统可能有多角度分类,每一种分类都有可能变化,那么就把这种多分角度分离出来让他们独立变化。
  Abstraction定义接口,并且维护着对Implementor对象的指针或者引用;RefinedAbstraction扩充由Abstraction定义的接口;在Implementor中规定实现类的接口,其接口不一定要与Abstration的接口完全一致,而且通常两者的接口完全不同,一般Implementor接口仅提供基本操作,而Abstration定义了基于这些基本操作较高层次的操作;ConcreteImplementorA,B…负责实现Implementor接口并定义它的具体实现。
  具体的例子:电脑制造商和操作系统,前者设计接口installOS(),后者实现接口installOS_impl(),然后两者可以分别独立的演化,在Computer中包含对OS的一个引用就可以调用具体的installOS_impl()了。再比如抽象Window和实现WindowsImpl,前者包含后者对象的一个引用imp,那么Icon/TransientWindow的任何操作都可以调用imp->XXX()来实现了。

2.3 组合(Composite)模式

  定义:将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。处理的是整体和部分可以被一致对待的问题。
bridge
  Component为组合中的对象声明接口,在适当情况下实现所有类公有接口的默认行为,同时也提供访问和管理(Add/Remove)Component的子部件;Leaf在组合中表示叶节点对象,叶节点没有子节点;Composite定义有子部件的那些部件的行为,存储子部件,同时在Component接口中实现与子部件有关的操作。
  Composite模式的关键是一个抽象类,它既可以代表元素,又可以代表元素的容器,即支持元素类型的一些操作,又有一些额外的操作访问和管理他的子部件。当需求中是体现部分与整体层次结构的时候,以及希望用户可以忽略组合对象和单个对象的不同,统一地使用组合结构中的所有对象时,就应该考虑使用组合模式了。
  举个例子就是总公司和分公司以及各个部门的关系,Company列出接口,而ConcreteCompany可以进行Add/Remove操作,同时XXXDepartment不支持Add/Remove操作。

2.4 装饰(Decorator)模式

  定义:动态地给一个对象添加一些额外的职责,就增加的功能来说,装饰模式比生成子类更为灵活。
decorator
  虽然通过继承机制是添加功能的一种有效途径,但是这种方式不够灵活,因为继承关系是静态的,用户就不能控制添加功能的方式和时机,解决的方式是将组件嵌入到另一个对象中,由这个对象实现额外功能,这个嵌入的对象就叫做装饰。对Component类本身来说,是无需知道Decorator的存在的(组件无需对他的装饰有任何的了解,而Strategy模式中component组件本身知道可能进行哪些扩充,必须引用和维护相应的策略,这是两者最大的区别),但是通过Decorator却起到给Component增加职责的功能,而且其对于使用该组件的客户是透明的,客户的请求被转发到该组件,透明性还允许嵌套多个装饰,实现任意多的功能。
  Component定义一个对象的接口,可以给这些对象动态地增加一些职责;ConcreteDecorator可以通过实现Component定义的接口,给对象添加一些具体的职责;Decorator维持一个指向Component对象的指针或者引用,并定义一个与Component接口一致的接口;ConcreteDecorator用于向组件添加职责。
  装饰类通过接口SetComponent()来设置需要装饰的对象(实参可以是Component或者更加具体的ConcreteComponent,指针或者引用类型),其参数必须是Component,这样就可以把装饰后的ConcreteDecorator再进装饰形成具有顺序的多次装饰了。这里提取两个抽象层,是为了扩展的方便,如果简单的话,是不需要提取Component和/或Decorator抽象类的。
  实践中当需要新功能的时候,可以向旧类直接添加代码,但是这些代码应当是原有类的核心或者主要职责功能,但想主类增加字段、方法、逻辑会增加主类的复杂度。对于某些在特定情况下才会执行的特殊行为需要,可以将装饰的功能放在单独的类当中,并让这个类包装它需要装饰的对象,客户代码可以在运行的时候根据需要有选择、按顺序地使用装饰功能包装对象了。 这样的好处就是把类的核心职能和装饰功能分开,同时能去除相关类中重复的装饰逻辑。
  实例比如Phone类,在产生具体的NokiaPhone、iPhone的核心功能子对象的同时,如果需要额外的装饰,可以依据Phone派生出DecPhone1、DecPhone2等装饰子类,然后通过调用DecPhone(nokiaPhone)就可以实现装饰了。

2.5 外观(Facade)模式

  定义:为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,每个接口使得这一子系统更加容易使用。
  Facade知道哪些子系统类负责处理请求,同时将客户的请求代理给适当的子系统对象。
  比如在设计和开发阶段,应当在数据访问层、业务逻辑层、表示层之间建立外观Facade,这样可以为复杂的子系统提供一个简单的接口,降低层层之间的耦合;
  在维护阶段,对于一个大型系统,可以为新系统开发一个外观类,来为设计粗糙或者高度复杂的系统提供一个简单清晰的接口;
  外观模式的特点是:(1)它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便;(2)它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的;(3)如果应用需要,它并不限制它们使用子系统类,可以自由的在易用性和通用性之前权衡。

2.6 享元(Flyweight)模式

  定义:运用共享技术有效的支持大量细粒度的对象。
flyweight
  享元模式可以避免大量非常相似类的开销,比如文档中的每个文字用对象来表示,大量的对象会产生很大的内存开销。在程序设计中,有时候需要生成大量细粒度的类实例来表示数据,如果能发现这些实例除了几个参数外基本上都是相同的,有时就能够大幅度的减少需要实例化的类的数量。把那些参数移到类实例的外面,在方法调用的时候再将它们传递进来,在满足相同效果的同时就可以通过共享大幅度地减少单个实例的数目。
  对于享元类,那些不会随环境改变而改变的共享部分,称为享元对象的内部状态,而随着环境改变而改变的,不可以共享的状态称为外部状态。如果程序使用大量的共享对象,而大量对象造成了很大的存储开销就应该考虑使用享元,还有就是对象的大多数状态可以作为外部状态,如果删除外部状态,那么可以用相对较少的共享对象取代很多种情况的组合对象,也考虑使用享元。
  Flyweight类是所有具体享元类的超类或者接口Operation(var),通过这个接口,Flyweight可以接收并作用于外部状态;ConcreteFlyweight实现Flyweight所定义的接口,并为内部状态增加容器存储空间,ConcreteFlyweight对象必须是可共享的,它所存储的状态必须是内部的;UnsharedConcreteFlyweight是那些不需要共享的Flyweight的子类;FlyweightFactory是一个享元工厂,用来创建并管理Flyweight对象,他确保合理地共享Flyweight,当用户请求一个Flyweight的时候,负责返回一个已创建的实例或者创建一个新的对象。
  实例比如棋盘的棋子Piece,颜色作为内在属性可以创建Black/WhitePiece,同时棋子的位置在棋盘上是外在属性,可以存储在std::vector中,此时std::vector存储的只是位置信息而非棋子对象,所以效率会提高不少。

2.7 代理(Proxy)模式

  定义:为其他对象提供一种代理以控制对这个对象的访问。
proxy
  Subject定义指明了RealSubject和Proxy的公用接口(比如show()方法),RealSubject和Proxy都需要继承Subject这个类,这样在任何使用RealSubject的地方都可以使用Proxy进行代理;Proxy类保存一个RealSubject的引用,从而使得代理可以访问代理实体,提供一个与Subject的接口相同的接口,这样代理就可以用来代替实体,在用户调用接口方法的时候,通过引用对象调用其具体的同名接口方法。
  远程代理:远程代理,为一个对象在不同的地址空间提供局部代表,以隐藏一个对象在不同地址空间的事实。
  虚拟代理:用于根据需要创建开销很大的对象,通过它来存放实例化需要很长世间的真实对象,这些对象在需要使用的时候才会被创建。
  安全代理:用来控制真实对象访问时候的权限,一般用于对象应该有不同访问权限的时候。
  智能指引:当调用真实对象的时候,代理额外处理一些事情,比如引用计数,锁定等。
  实例的话,就想想SmartPointer就好啦!

本文完!

参考