结构模式描述的是如何和组合类和对象已获得更大的结构。类模式和对象模式之间的区别是:类描述的如何通过继承提供更有用的接口,而对象描述的是通过使用对象的组合或将对象包涵在别的对象里面以获得更有用的结构。
适配器模式:可以将类的一个借口匹配另一个接口
组合模式:对象的组合
代理模式:一个简单的对象代替一个复杂的稍后会被调用的复杂对象
外观模式:一个类表示一个子系统
享元模式:用于共享对象,其中每个实例都不保存自己的状态。而是将状态保存在外部
桥接模式:将对象的接口与实现分离
装饰模式:动态给对象添加职责
适配器模式:将一个类的程序设计接口转换成另一个接口。
定义:编写一个具有所需要接口的类,由他和拥有不同接口的类进行通信
意图:复用已存在的接口与所需接口不一致的类
实现:
-
类适配器:从一个不一致的类派生出一个类,然后自爱派生类里面增加所需要的方法,使得派生类能够匹配所需要的接口。
-
对象适配器:将原始类包含在新类里,然后在新类里创建方法去转换调用。
两者比较:
角色及职责:
-
源(Adaptee):已经存在的、需要适配的类。
-
目标(Target):客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口。
-
适配器(Adapter):适配器模式的核心类。有两种方式实现Adapter,对象适配器(Object Adapter)和类适配器(Class Adapter)。
-
客户(Client):
UML图:
补充:更高层次的适配器
从左图可以看出:Adapter将Adaptee接口适配为客户Client需要的接口Target,这样在整个系统中所有实现Adaptee接口的类都可以通过Adapter适配为Target对象,从而避免为每一个类都写一个适配器。后面会给大家带来一个JDK中使用此中适配的例子。我们不仅仅可以象上面一样对接口进行适配,也可以对抽象类进行适配!主要是根据系统的需求,确定此时的场景是否适合使用适配器模式!
适用范围:
-
系统需要使用现有的类,而此类的接口不符合系统的需要
-
想要建立一个可以重复使用的类,用于与一些彼此之间没有太大关联的一些类,包括一些可能在将来引进的类一起工作。这些源类不一定有很复杂的接口。
-
(对对象适配器而言)在设计里,需要改变多个已有子类的接口,如果使用类的适配器模式,就要针对每一个子类做一个适配器,而这不太实际。
桥接模式:
意图:将抽象部分与实现部分分离,使得她们两个部分可以独立的变化。
等级结构:
- 由抽象化角色和修正抽象化角色组成的抽象化等级结构。
- 由实现化角色和两个具体实现化角色所组成的实现化等级结构。
角色及职责:
- 抽象化(Abstraction)角色:抽象化给出的定义,并保存一个对实现化对象的引用。
- 修正抽象化(Refined Abstraction)角色:扩展抽象化角色,改变和修正父类对抽象化的定义。
- 实现化(Implementor)角色:这个角色给出实现化角色的接口,但不给出具体的实现。必须指出的是,这个接口不一定和抽象化角色的接口定义相同,实际上,这两个接口可以非常不一样。实现化角色应当只给出底层操作,而抽象化角色应当只给出基于底层操作的更高一层的操作。
-
具体实现化(Concrete Implementor)角色: 这个角色给出实现化角色接口的具体实现。
优势和缺陷:
桥接模式可以从接口分离实现功能,使得设计更具有扩展性,这样,客户调用方法是根本不需要知道实现的细节。桥接默哀是减少了子类,如果程序中要在2个操作系统中实现查看6种图像格式,那么就会有2*6个类。使用桥接模式时候就会变成2+6个类了,,它使代码变得更清洁了,生成的执行程序更小了。但是桥接模式的缺陷是抽象类与实现类的双向连接使得运行速度更慢了。
适用场景:
- 避免抽象方法和其实现方法绑定在一起。
- 抽象接口和它的实现都需要扩展出子类以备使用
- 变动实现的方法根本不会影响客户程序调用的部分(甚至不用重新编译)。
组合模式:组合模式有时候又叫做部分-整体模式,它使我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
意图:将对象组合成树形结构以表示"部分-整体"的层次结构。Composite模式使得用户对单个对象和组合对象的使用具有一致性。
透明模式
安全模式
分类:
- 安全式的组合模式:管理聚集的方法只出现在组合对象中,而不出现在简单对象中。
- 透明式的组合模式:所有的对象,不论组合对象还是简单对象,均符合一个固定的接口。
效果及实现要点:
- Composite模式采用树形结构来实现普遍存在的对象容器,从而将"一对多"的关系转化"一对一"的关系,使得客户代码可以一致地处理对象和对象容器,无需关心处理的是单个的对象,还是组合的对象容器。
- 将"客户代码与复杂的对象容器结构"解耦是Composite模式的核心思想,解耦之后,客户代码将与纯粹的抽象接口——而非对象容器的复内部实现结构——发生依赖关系,从而更能"应对变化"。
- Composite模式中,是将"Add和Remove等和对象容器相关的方法"定义在"表示抽象对象的Component类"中,还是将其定义在"表示对象容器的Composite类"中,是一个关乎"透明性"和"安全性"的两难问题,需要仔细权衡。这里有可能违背面向对象的"单一职责原则",但是对于这种特殊结构,这又是必须付出的代价。ASP.NET控件的实现在这方面为我们提供了一个很好的示范。
- Composite模式在具体实现中,可以让父对象中的子对象反向追溯;如果父对象有频繁的遍历需求,可使用缓存技巧来改善效率。
适用性:
- 你想表示对象的部分-整体层次结构
- 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
装饰模式:动态地给对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更灵活。装饰模式是为已有的功能动态地添加更多功能的一种方式。在起初的设计当中,当系统需要新功能的时候,是向旧的类中添加新的代码,这些新加的代码,通常装饰了原有类的核心职责或主要行为,在主类中加入了新的字段,新的方法和新的逻辑,从而增加了主类的复杂度,而这些新加入的东西仅仅是为了满足一些只在某种特定情况下才会执行的特殊行为的需要。而装饰模式却提供了一个非常好的解决方案,它把每个要装饰的功能都放在单纯的类中,并让这个类包装它所要装饰的对象(就是抽象类Decorator中的Component字段),因此,当需要执行特殊行为时,客户代码就可以在运行时根据需要有选择地、按顺序地使用装饰功能包装对象了。
角色及职责:
- 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
- 具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。
- 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
- 具体装饰(Concrete Decorator)角色:负责给构件对象"贴上"附加的责任。
特点:
- 装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互
- 装饰对象包含一个真实对象的索引(reference)
- 装饰对象接受所有的来自客户端的请求。它把这些请求转发给真实的对象
- 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展
适用范围:
- 需要扩展一个类的功能,或给一个类增加附加责任。
- 需要动态地给一个对象增加功能,这些功能可以再动态地撤销。
- 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变得不现实。
优点:
- 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。
- 通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。
- 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错。
缺点:
- 由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。
模式实现的
- 一个装饰类的接口必须与被装饰类的接口相容。
- 尽量保持Component作为一个"轻"类,不要把太多的逻辑和状态放在Component类里。
- 如果只有一个ConcreteComponent类而没有抽象的Component类(接口),那么Decorator类经常可以是ConcreteComponent的一个子类。
- 如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。
-
透明性的要求
透明的装饰模式装饰模式通常要求针对抽象编程。装饰模式对客户端的透明性要求程序不要声明一个ConcreteDecorator类型的变量,而应当声明一个Component类型的变量。换言之,下面的做法是对的:
Component c = new ConcreteComponent();
Component c1 = new ConcreteDecorator1(c);
Component c2 = new ConcreteDecorator(c1);而下面的做法是不对的:
ConcreteComponent c = new ConcreteDecorator();
这就是前面所说的,装饰模式对客户端是完全透明的含义。
-
半透明的装饰模式
然而,纯粹的装饰模式很难找到。装饰模式的用意是在不改变接口的前提下,增强所考虑的类的性能。在增强性能的时候,往往需要建立新的公开的方法。即便是在孙大圣的系统里,也需要新的方法。比如齐天大圣类并没有飞行的能力,而雀儿有。这就意味着雀儿应当有一个新的fly()方法。这就导致了大多数的装饰模式的实现都是"半透明"(semi-transparent)的,而不是完全"透明"的。换言之,允许装饰模式改变接口,增加新的方法。即声明ConcreteDecorator类型的变量,从而可以调用ConcreteDecorator类中才有的方法。
外观模式:
意图:为子系统提供了一个更高层次、更简单的接口,从而降低了子系统的复杂度和依赖。这使得子系统更易于使用和管理。对外提供一个统一的接口用来访问子系统中的一群接口。外观是一个能为子系统和客户提供简单接口的类。当正确的应用外观,客户不再直接和子系统中的类交互,而是与外观交互。外观承担与子系统中类交互的责任。实际上,外观是子系统与客户的接口,这样外观模式降低了子系统和客户的耦合度。外观对象隔离了客户和子系统对象,从而降低了耦合度。当子系统中的类进行改变时,客户端不会像以前一样受到影响。
角色及职责:
- 门面(Facade)这个外观类为子系统中Packages 1、2、3提供一个共同的对外接口
- 客户(Clients)客户对象通过一个外观接口读写子系统中各接口的数据资源。
- 子系统(Packages)客户可以通过外观接口读取的内部库。子系统并不知道门面的存在,对于子系统而言,门面仅仅是另外一个客户端而已。
适用范围:
- 不需要使用一个复杂系统的所有功能,而且可以创建一个新的类,包含访问系统的所有规则。如果只需要使用系统的部分功能,那么你为新类所创建的API将比原系统的API简单的多。
- 希望封装或者隐藏系统原系统。
- 希望使用原系统的功能,而且还希望增加一些新的功能。
- 编写新类的成本小于所有人学会使用或者未来维护原系统上所需的成本。
享元模式:用一个共享来避免大量拥有相同内容对象的开销。这种开销中最常见、直观的就是内存的损耗。享元模式以共享的方式高效的支持大量的细粒度对象。
对象的状态:
-
内蕴状态(Internal State)内蕴状态存储在享元对象内部且不会随环境改变而改变。因此内蕴状态并可以共享。
-
外蕴状态(External State)。外蕴状态是随环境改变而改变的、不可以共享的状态。享元对象的外蕴状态必须由客户端保存,并在享元对象被创建之后,在需要使用的时候再传入到享元对象内部。外蕴状态与内蕴状态是相互独立的。
适用范围:
-
一个系统有大量的对象。
-
这些对象耗费大量的内存。
-
这些对象的状态中的大部分都可以外部化。
-
这些对象可以按照内蕴状态分成很多的组,当把外蕴对象从对象中剔除时,每一个组都可以仅用一个对象代替。
-
软件系统不依赖于这些对象的身份,换言之,这些对象可以是不可分辨的。
分类:
-
单纯享元模式
角色及职责:
1>:抽象享元(Flyweight)角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过调用商业方法以参数形式传入
/// <summary>
/// "Flyweight"
/// </summary>
abstract class Flyweight
{
// Methods
/// <summary>
/// 抽象享元对象的商业方法
/// </summary>
/// <param name="extrinsicstate">外蕴状态</param>
abstract public void Operation(int extrinsicstate);
}
2>:具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享的。
/// <summary>
/// "ConcreteFlyweight"
/// </summary>
class ConcreteFlyweight : Flyweight
{
private string intrinsicstate = "A";
// Methods
override public void Operation(int extrinsicstate)
{
Console.WriteLine("ConcreteFlyweight: intrinsicstate {0}, extrinsicstate {1}",intrinsicstate, extrinsicstate);
}
}
3>:享元工厂(FlyweightFactory)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象调用一个享元对象的时候,享元工厂角色会检查系统中是否已经有一个符合要求的享元对象。如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。
注意:客户端不可以直接实例化享元类,必须通过享元工厂类来创建,因为享元工厂类在系统中只能有一个,所以可以结合单件模式来改善。当客户端需要单纯享元对象时,需要调用享元工厂的Singleton()方法,此时工厂会取得所有的单纯享元对象,然后传入所需的单纯享元对象的内蕴状态,工厂方法负责产生所需要的享元对象。
/// <summary>
/// "FlyweightFactory"
/// </summary>
class FlyweightFactory
{
// Fields
private Dictionary<string, Flyweight> flyweights = new Dictionary<string, Flyweight>();
private static readonly FlyweightFactory instance = new FlyweightFactory();
/// <summary>
/// Constructors
/// </summary>
private FlyweightFactory()
{
}
// Methods
/// <summary>
/// 从享元工厂中生产出一个具体的享元对象
/// </summary>
/// <param name="key">内蕴状态</param>
/// <returns></returns>
public Flyweight GetFlyweight(string key)
{
return ((Flyweight)flyweights[key]);
}
/// <summary>
/// 享元工厂单例方法
/// </summary>
/// <returns></returns>
public static FlyweightFactory Singleton()
{
return FlyweightFactory.instance;
}
/// <summary>
/// 向享元工厂对象增加一个享元对象
/// </summary>
/// <param name="sKey">内蕴状态</param>
/// <param name="_Flyweight">具体享元对象</param>
public void AddFlyweight(string sKey, Flyweight _Flyweight)
{
flyweights.Add(sKey , _Flyweight);
}
public Flyweight factory(string sKey)
{
if (flyweights.ContainsKey(sKey))
{
return this.GetFlyweight(sKey);
}
else
{
this.AddFlyweight(sKey, new ConcreteFlyweight());
return this.GetFlyweight(sKey);
}
}
}
4>:客户端(Client)角色:需要维护一个对所有享元对象的引用;需要自行存储所有享元对象外蕴状态。
// 初始化外蕴状态值
int extrinsicstate = 22;
//享元工厂对象使用单例
FlyweightFactory f = FlyweightFactory.Singleton () ;
//调用过程
//向享元工厂对象请求一个内蕴状态为"X"的单纯享元对象
Flyweight fx = f.factory("X");
//调用X的商业方法,X的外蕴状态值为21
fx.Operation(--extrinsicstate);
Flyweight fy = f.factory("Y");
fy.Operation(--extrinsicstate);
Flyweight fz = f.factory("Z");
fz.Operation(--extrinsicstate);
-
复合享元模式。
1>:抽象享元角色:此角色是所有的具体享元类的超类,为这些类规定出需要实现的公共接口。那些需要外蕴状态(External State)的操作可以通过方法的参数传入。抽象享元的接口使得享元变得可能,但是并不强制子类实行共享,因此并非所有的享元对象都是可以共享的。
2>:具体享元(ConcreteFlyweight)角色:实现抽象享元角色所规定的接口。如果有内蕴状态的话,必须负责为内蕴状态提供存储空间。享元对象的内蕴状态必须与对象所处的周围环境无关,从而使得享元对象可以在系统内共享。有时候具体享元角色又叫做单纯具体享元角色,因为复合享元角色是由单纯具体享元角色通过复合而成的。
3>:复合享元(UnsharableFlyweight)角色:复合享元角色所代表的对象是不可以共享的,但是一个复合享元对象可以分解成为多个本身是单纯享元对象的组合。复合享元角色又称作不可共享的享元对象。
4>:享元工厂(FlyweightFactoiy)角色:本角色负责创建和管理享元角色。本角色必须保证享元对象可以被系统适当地共享。当一个客户端对象请求一个享元对象的时候,享元工厂角色需要检查系统中是否已经有一个符合要求的享元对象,如果已经有了,享元工厂角色就应当提供这个已有的享元对象;如果系统中没有一个适当的享元对象的话,享元工厂角色就应当创建一个新的合适的享元对象。
5>:客户端(Client)角色:本角色还需要自行存储所有享元对象的外蕴状态。优点: 大幅度地降低内存中对象的数量。
缺点:
-
享元模式使得系统更加复杂。为了使对象可以共享,需要将一些状态外部化,这使得程序的逻辑复杂化。
-
享元模式将享元对象的状态外部化,而读取外部状态使得运行时间稍微变长。
总结:
享元模式一般是解决系统性能问题的,所以经常用于底层开发,在项目开发中并不常用.
-
代理模式:
意图:为其他对象提供一种代理以控制对这个对象的访问。
角色及职责:
-
抽象主题角色:声明了真实主题和代理主题的共同接口。
-
代理主题角色:内部包含对真实主题的引用,并且提供和真实主题角色相同的接口。
-
真实主题角色:定义真实的对象。
适用范围:
- 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个局域代表对象。比如:你可以将一个在世界某个角落一台机器通过代理假象成你局域网中的一部分。
- 虚拟(Virtual)代理:根据需要将一个资源消耗很大或者比较复杂的对象延迟的真正需要时才创建。比如:如果一个很大的图片,需要花费很长时间才能显示出来,那么当这个图片包含在文档中时,使用编辑器或浏览器打开这个文档,这个大图片可能就影响了文档的阅读,这时需要做个图片Proxy来代替真正的图片。
- 保护(Protect or Access)代理:控制对一个对象的访问权限。比如:在论坛中,不同的身份登陆,拥有的权限是不同的,使用代理模式可以控制权限(当然,使用别的方式也可以实现)。
- 智能引用(Smart Reference)代理:提供比对目标对象额外的服务。比如:纪录访问的流量(这是个再简单不过的例子),提供一些友情提示等等。
实现:
- 指明一系列接口创建一个代理对象
- 创建调用处理器对象
- 将这个代理指定为其他代理的代理对象
在调用处理器的invoke()方法中采取处理,一方面将调用传递给真是对象,另一方面执行各种所需要的操作。