23设计模式总结

0) 前言

0-1) 设计模式的六大原则

总原则:开闭原则(Open Close Principle)

开闭原则就是说对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是要扩展原有代码,实现一个热插拔的效果。所以一句话概括就是:为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类等,后面的具体设计中我们会提到这点。

0-1-1) 单一职责原则 (Single responsibility principle)

不要存在多于一个导致类变更的原因,也就是说每个类应该实现单一的职责,如若不然,就应该把类拆分。

0-1-2) 里氏替换原则 (Liskov Substitution Principle)

里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。 LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

历史替换原则中,子类对父类的方法尽量不要重写和重载。因为父类代表了定义好的结构,通过这个规范的接口与外界交互,子类不应该随便破坏它。

0-1-3) 依赖倒转原则 (Dependence Inversion Principle)

这个是开闭原则的基础,具体内容:面向接口编程,依赖于抽象而不依赖于具体。写代码时用到具体类时,不与具体类交互,而与具体类的上层接口交互。

0-1-4) 接口隔离原则 (Interface Segregation Principle)

这个原则的意思是:每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分。使用多个隔离的接口,比使用单个接口(多个接口方法集合到一个的接口)要好。

0-1-5) 迪米特法则(最少知道原则) (Demeter Principle)

就是说:一个类对自己依赖的类知道的越少越好。也就是说无论被依赖的类多么复杂,都应该将逻辑封装在方法的内部,通过public方法提供给外部。这样当被依赖的类变化时,才能最小的影响该类。

最少知道原则的另一个表达方式是:只与直接的朋友通信。类之间只要有耦合关系,就叫朋友关系。耦合分为依赖、关联、聚合、组合等。我们称出现为成员变量、方法参数、方法返回值中的类为直接朋友。局部变量、临时变量则不是直接的朋友。我们要求陌生的类不要作为局部变量出现在类中。

0-1-6)合成复用原则 (Composite Reuse Principle)

原则是尽量首先使用合成/聚合的方式,而不是使用继承。

0-2) 设计模式分类

名称 创建型模式 Creational Pattern 结构型模式 Structural Pattern 行为型模式 Behavioral Pattern
概念 创建型模式,就是创建对象的模式,抽象了实例化的过程。它帮助一个系统独立于如何创建、组合和表示它的那些对象。关注的是对象的创建,创建型模式将创建对象的过程进行了抽象,也可以理解为将创建对象的过程进行了封装,作为客户程序仅仅需要去使用对象,而不再关心创建对象过程中的逻辑 结构型模式是为解决怎样组装现有的类,设计他们的交互方式,从而达到实现一定的功能的目的。结构型模式包容了对很多问题的解决。例如:扩展性(外观、组成、代理、装饰)封装性(适配器,桥接) 行为型模式涉及到算法和对象间职责的分配,行为模式描述了对象和类的模式,以及它们之间的通信模式,行为型模式刻划了在程序运行时难以跟踪的复杂的控制流可分为行为类模式和行为对象模式1.行为模式使用继承机制在类间分派行为2.行为对象模式使用对象聚合来分配行为。一些行为对象模式描述了一组对等的对象怎样相互协作以完成其中任何一个对象都无法单独完成的任务。
Factory Method Adapter(类) Interpreter
Template Method
Abstract Factory Adapter(对象) Chain of Responsibility
Builder Bridge Command
Prototype Composite Iterator
Singleton Decorator Mediator
Facade Memento
Flyweight Observer
Proxy State
Strategy
Visitor

1) 创建型模式

1-1) Factory 模式

1-1-1) 定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。

1-1-2) 详解

第一种功能,定义接口,Factory 的结构示意图:
23设计模式总结

1-1-3) 场景

  • 当一个类不知道它所必须创建的对象的类的时候。
  • 当一个类希望由它的子类来指定它所创建的对象的时候。
  • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

1-1-4) 总结

Factory 模式在实际开发中应用非常广泛,面向对象的系统经常面临着对象创建问题: 要创建的类实在是太多了。而 Factory 提供的创建对象的接口封装(第一个功能),以及其 将类的实例化推迟到子类(第二个功能)都部分地解决了实际问题。

但这不是Factory功能的最大优点,真正的优点是第二个功能,子类的延迟实例化,如下图:
23设计模式总结

图 2 中关键中 Factory 模式的应用并不是只是为了封装对象的创建,而是要把对象的创
建放到子类中实现:Factory 中只是提供了对象创建的接口,其实现将放在 Factory 的子类 ConcreteFactory 中进行。这是图 2 和图 1 的区别所在。

1-2) AbstractFactory 模式

1-2-1) 定义

AbstractFactory 模式就是提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

1-2-2) 详解

AbstractFactory 模式典型的结构图为:
23设计模式总结

  • AbstractFactory (抽象工厂)
    • 声明一个创建抽象产品对象的操作接口。
  • ConcreteFactory (具体工厂) 

    • 实现创建具体产品对象的操作。 

  • AbstractProduct (抽象产品)
    • 为一类产品对象声明一个接口。 

  • ConcreteProduct (具体产品)
    • 定义一个将被相应的具体工厂创建的产品对象。
    • 实现AbstractProduct接口。
  • Client(使用场景)
    • 仅使用由AbstractFactory和AbstractProduct类声明的接口。


1-2-3) 场景

• 一个系统要独立于它的产品的创建、组合和表示时。
• 一个系统要由多个产品系列中的一个来配置时。
• 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
• 当你提供一个产品类库,而只想显示它们的接口而不是实现时。

1-2-4) 总结

工厂方法模式和抽象工厂模式的区别
简单工厂模式:

  • 只有一个工厂类一个生产方法,根据参数不同生产不同的产品。

工厂方法模式:

  • 每一个工厂类只负责一个产品生产,不生成其它产品。好比一条生产线只生产一个产品线。

抽象工厂模式:

  • 每一个工厂类提供多个方法,可以生产不同的产品。好比多条生产线可以生产多家产品。

1-3) Singleton 模式

1-3-1) 定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

1-3-2) 详解

Singleton 模式典型的结构图为:
23设计模式总结
在 Singleton 模式的结构图中可以看到,我们通过维护一个 static 的成员变量来记录这
个唯一的对象实例。通过提供一个 staitc 的接口 instance 来获得这个唯一的实例。

1-3-3) 场景

  • 当类只有一个实例而且客户可以从一个众所周知的访问点访问它时。
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

1-3-4) 总结

开发中使用单例模式,有几点要注意:
1. 只创建一个实例,并且只提供一个全局的访问点;避免创建多个实例的可能。
2. 资源共享情况下,获取实例的方法必须适应多线程并发访问。
3. 提高访问性能。
4. 懒加载(Lazy Load),在需要的时候才被构造。。

1-4) Builder 模式

1-4-1) 定义

将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

1-4-2) 详解

Builder 模式的典型结构图为:
23设计模式总结

  • Builder(抽象建造者角色)
    • 为创建一个Product对象的各个部件指定抽象接口。
  • ConcreteBuilder(具体建造者)
    • 实现Builder的接口以构造和装配该产品的各个部件。
    • 定义并明确它所创建的表示。
    • 提供一个检索产品的接口。
  • Director(导演角色)
    • 构造一个使用Builder接口的对象。
  • Product(建造的产品)
    • 表示被构造的复杂对象。 ConcreteBuilder创建该产品的内部表示并定义它的装配过程。
    • 包含定义组成部件的类,包括将这些部件装配成最终产品的接口。

1-4-3) 场景

  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
  • 当构造过程必须允许被构造的对象有不同的表示时。

1-4-4) 总结

当创造一个对象需要很多步骤时适合使用建造者模式。
而当只需调用一个方法就可以简单地创建整个对象时适合使用工厂模式。

1-5) Prototype 模式

1-5-1) 定义

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

1-5-2) 详解

23设计模式总结

  • Prototype(原型): 声明一个克隆自身的接口。
  • ConcretePrototype(具体的原型): 实现一个克隆自身的操作。
  • Client(场景):让一个原型克隆自身从而创建一个新的对象。

1-5-3) 场景

• 当要实例化的类是在运行时刻指定时,例如,通过动态装载;
• 为了避免创建一个与产品类层次平行的工厂类层次时;
• 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。

1-5-4) 总结

Prototype 模式通过复制原型(Prototype)而获得新对象创建的功能,这里 Prototype 本身就是“对象工厂”(因为能够生产对象),实际上 Prototype 模式和 Builder 模式、 AbstractFactory 模式都是通过一个类(对象实例)来专门负责对象的创建工作(工厂对象), 它们之间的区别是:
Builder 模式重在复杂对象的一步步创建(并不直接返回对象)
AbstractFactory 模式重在产生多个相互依赖类的对象
Prototype 模式重在从自身复制自己创建新类。

1-5-4-1) 优点:

性能优良。原型模式是在内存二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。

1-5-4-2) 缺点:

逃避构造函数的约束。这既是它的优点也是缺点,直接在内存中拷贝,构造函数是不会执行的。优点就是减少了约束,缺点也是减少了约束,需要大家在实际应用时考虑。

2) 结构型模式

2-1) Bridge 模式

2-1-1) 定义

将抽象部分与它的实现部分分离,使它们都可以独立地变化。

2-1-2) 详解

Bridge 模式典型的结构图为:
23设计模式总结

  • 抽象类(Abstraction):定义抽象类的接口,维护一个指向AbstractionImpl类型对象的指针
  • 扩充抽象类(RefinedAbstraction):扩充由Abstraction定义的接口
  • 实现类接口(AbstractionImpl):定义实现类的接口,该接口不一定要与Abstraction的接口完全一致;事实上这两个接口可以完全不同。一般来讲, AbstractionImpl接口仅提供基本操作,而 Abstraction则定义了基于这些基本操作的较高层次的操作。
  • 具体实现类(ConcreteAbstraction):实现AbstractionImpl接口并定义它的具体实现。

理解桥接模式,重点需要理解如何将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化。

  • 抽象化:抽象化就是忽略一些信息,把不同的实体当作同样的实体对待。在面向对象中,将对象的共同性质抽取出来形成类的过程即为抽象化的过程。
  • 实现化:针对抽象化给出的具体实现,就是实现化,抽象化与实现化是一对互逆的概念,实现化产生的对象比抽象化更具体,是对抽象化事物的进一步具体化的产物。
  • 脱耦:脱耦就是将抽象化和实现化之间的耦合解脱开,或者说是将它们之间的强关联改换成弱关联,将两个角色之间的继承关系改为关联关系。桥接模式中的所谓脱耦,就是指在一个软件系统的抽象化和实现化之间使用关联关系(组合或者聚合关系)而不是继承关系,从而使两者可以相对独立地变化,这就是桥接模式的用意。

2-1-3) 实例

2-1-3-1) 笔和颜料

现需要提供大中小3种型号的画笔,能够绘制5种不同颜色,如果使用蜡笔,我们需要准备3*5=15支蜡笔,也就是说必须准备15个具体的蜡笔类。而如果使用毛笔的话,只需要3种型号的毛笔,外加5个颜料盒,用3+5=8个类就可以实现15支蜡笔的功能。
实际上,蜡笔和毛笔的关键一个区别就在于笔和颜色是否能够分离。即将抽象化(Abstraction)与实现化(Implementation)脱耦,使得二者可以独立地变化”。关键就在于能否脱耦。蜡笔的颜色和蜡笔本身是分不开的,所以就造成必须使用15支色彩、大小各异的蜡笔来绘制图画。而毛笔与颜料能够很好的脱耦,各自独立变化,便简化了操作。在这里,抽象层面的概念是:”毛笔用颜料作画”,而在实现时,毛笔有大中小三号,颜料有红绿蓝黑白等5种,于是便可出现3×5种组合。每个参与者(毛笔与颜料)都可以在自己的自由度上随意转换。
蜡笔由于无法将笔与颜色分离,造成笔与颜色两个自由度无法单独变化,使得只有创建15种对象才能完成任务。
Bridge模式将继承关系转换为组合关系,从而降低了系统间的耦合,减少了代码编写量。
UML如图:
23设计模式总结

2-1-3-2) 跨平台视频播放器:两个维度的变化,平台和不同格式的视频文件:

23设计模式总结

2-1-4) 总结

  • 优点:

    1. 分离接口及其实现部分 一个实现未必不变地绑定在一个接口上。抽象类的实现可以在运行时刻进行配置,一个对象甚至可以在运行时刻改变它的实现。将Abstraction与Implementor分离有助于降低对实现部分编译时刻的依赖性,当改变一个实现类时,并不需要重新编译 Abstraction类和它的客户程序。为了保证一个类库的不同版本之间的二进制兼容性,一定要有这个性质。另外,接口与实现分离有助于分层,从而产生更好的结构化系统,系统的高层部分仅需知道Abstraction和Implementor即可。
    2. 提高可扩充性 你可以独立地对Abstraction和Implementor层次结构进行扩充。
    3. 实现细节对客户透明 你可以对客户隐藏实现细节,例如共享 Implementor对象以及相应的引用计数机制(如果有的话) 。
  • 桥接模式的缺点:

    1. 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
    2. 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。

简而言之:桥接模式是分离抽象化和实现,使两者的接口可以不同,目的是分离,使两个或两个以上的维度任意组合。每个维度的修改互不影响。

2-2) Adapter 模式

2-2-1) 定义

Adapter 模式: 将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的类可以一起工作。

2-2-2) 详解

Adapter 模式典型的结构图为:
23设计模式总结

  • Target (目标角色,要用的接口)
    定义Client使用的与特定领域相关的接口。

  • Client (使用场景)
    与符合Target接口的对象协同。

  • Adaptee (被适配者)
    定义一个已经存在的接口,这个接口需要适配。

  • Adapter (适配器,把Adaptee接口转换为Target可用的接口)
    对Adaptee的接口与Target接口进行适配

2-2-3) 场景

  • 你想使用一个已经存在的类,而它的接口不符合你的需求。
  • 你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。
  • (仅适用于对象Adapter)你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。

2-2-4) 总结

优点:
1. 适配器模式可以让两个没有任何关系的类在一起运行,只要适配器这个角色能够搞定他们就成。
2. 增加了类的透明性
想想看,我们访问的Target目标角色,但是具体的实现都委托给了源角色,而这些对高层次模块是透明的,也是它不需要关心的。
3. 提高了类的复用度当然了,源角色在原有的系统中还是可以正常使用,而在目标角色中也可以充当新的演员。
4. 灵活性非常好
某一天,突然不想要适配器,没问题,删除掉这个适配器就可以了,其他的代码都不用修改,基本上就类似一个灵活的构件,想用就用,不想就卸载。

2-3) Decorator 模式

2-3-1) 定义

动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。

2-3-2) 详解

Decorator Pattern叫装饰模式,或装饰者模式,以前叫包装器模式(Wrapper,GoF在92-93年由Wrapper改为Decorator)。
装饰模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

Decorator 模式典型的结构图为:
23设计模式总结

  1. 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。
  2. 具体构件(Concrete Component)角色:定义一个将要接收附加责任的类。
  3. 装饰(Decorator)角色:持有一个构件(Component)对象的实例,并实现一个与抽象构件接口一致的接口。
  4. 具体装饰(Concrete Decorator)角色:负责给构件对象添加上附加的责任。

2-3-3) 场景

  1. 需要扩展一个类的功能,或给一个类添加附加职责。
  2. 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
  3. 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
  4. 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。

2-3-4) 总结

Decorator 模式除了采用组合的方式取得了比采用继承方式更好的效果,Decorator 模式 还给设计带来一种“即用即付”的方式来添加职责。在 OO 设计和分析经常有这样一种情况: 为了多态,通过父类指针指向其具体子类,但是这就带来另外一个问题,当具体子类要添加 新的职责,就必须向其父类添加一个这个职责的抽象接口,否则是通过父类指针是调用不到 这个方法了。这样处于高层的父类就承载了太多的特征(方法),并且继承自这个父类的所 有子类都不可避免继承了父类的这些接口,但是可能这并不是这个具体子类所需要的。而在 Decorator 模式提供了一种较好的解决方法,当需要添加一个操作的时候就可以通过 Decorator 模式来解决,你可以一步步添加新的职责。

2-4) Composite 模式

2-4-1) 定义

将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。

2-4-2) 详解

Composite 模式的典型结构图为:
23设计模式总结

  1. Component 是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。
  2. Leaf 在组合中表示叶子结点对象,叶子结点没有子结点。
  3. Composite 定义有枝节点行为,用来存储子部件,在Component接口中实现与子部件有关操作,如增加(add)和删除(remove)等。

2-4-3) 场景

1.你想表示对象的部分-整体层次结构
2.你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象

2-4-4) 总结

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

2-5) Flyweight 模式

2-5-1) 定义

运用共享技术有效地支持大量细粒度的对象。

2-5-2) 详解

有些应用程序得益于在其整个设计过程中采用对象技术,但简单化的实现代价极大。
使用面向对象的抽象化,可能会造成庞大的对象群,造成空间的巨大消耗,而影响性能。
在文档编辑器例子中如果一个字符对应一个对象,那么一篇文档所要容纳的对象将是非常的庞大耗费大量的内存。
23设计模式总结

而实际组成文档的字符是有限的,是由这些字符不同的组合和排列得到的。
所以需要共享,将基本的字符进行共享,将使得字符对象变得有限。
23设计模式总结

Flyweight只存储相应的字符代码
这里的关键概念是内部状态和外部状态之间的区别。
  内部状态存储于flyweight中,它包含了独立于flyweight场景的信息,这些信息使得flyweight可以被共享。
如字符代码,字符大小……
  外部状态取决于flyweight场景,并根据场景而变化,因此不可共享。用户对象负责在必要的时候将外部状态传递给flyweight。
如字符位置,字符颜色……

典型的结构图为:
23设计模式总结

下面的对象图说明了如何共享 Flyweight:
23设计模式总结

Flyweight

  • 描述一个接口,通过这个接口 Flyweight 可以接受并作用于外部状态。

ConcreteFlyweight

  • 实现 Flyweight 接口,并为内部状态增加存储空间。该对象必须是可共享的。它所存储的状态必须是内部的,即必须独立于对象的场景。

UnsharedConcreteFlyweight

  • 并非所有的 Flyweight 子类都需要被共享。Flyweight 接口使共享成为可能,但它并不强制共享。

FlyweightFactory

  • 创建并管理 Flyweight 对象。
    确保合理地共享 Flyweight。

Client

  • 维持一个对 Flyweight 的引用。
    计算或存储 Flyweight 的外部状态。

2-5-3) 适用性

Flyweight 模式的有效性很大程度上取决于如何使用它以及在何处使用它。

当以下情况成立时可以使用 Flyweight 模式:

  • 一个应用程序使用了大量的对象。
  • 完全由于使用大量对象,造成很大的存储开销。
  • 对象的大多数状态都可变为外部状态。
  • 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
  • 应用程序不依赖于对象标识。由于Flyweight对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值

2-5-4) 总结

Flyweight 模式通常和 Composite 模式结合起来,用共享叶节点的又向无环图实现一个逻辑上的层次结构。
通常,最好用 Flyweight 实现 State 和 Strategy 对象。

2-6) Facade 模式

2-6-1) 定义

为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

2-6-2) 详解

我们通过 Facade 模式解决上面的问题,其典型的结构图为:
23设计模式总结

23设计模式总结

2-6-3) 场景

  • 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性, 也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade可以提供一个简单的缺省视图,这一视图对大多数用户来 说已经足够,而那些需要更多的可定制性的用户可以越过Facade层。
  • 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入Facade将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
  • 当你需要构建一个层次结构的子系统时,使用门面模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过Facade进行通讯,从而简化了它们之间的依赖关系。

2-6-4) 总结

Facade模式有下面一些优点:
1. 对客户屏蔽子系统组件,减少了客户处理的对象数目并使得子系统使用起来更加容易。通过引入外观模式,客户代码将变得很简单,与之关联的对象也很少。
2. 实现了子系统与客户之间的松耦合关系,这使得子系统的组件变化不会影响到调用它的客户类,只需要调整外观类即可。
3. 降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程,因为编译一个子系统一般不需要编译所有其他的子系统。一个子系统的修改对其他子系统没有任何影响,而且子系统内部变化也不会影响到外观对象。
4. 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类。

Facade模式的缺点
1. 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性。
2. 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”。

2-7) Proxy 模式

2-7-1) 定义

为其他对象提供一种代理以控制对这个对象的访问。

2-7-2) 详解

Proxy 模式典型的结构图为:
23设计模式总结
实际上,Proxy 模式的想法非常简单

2-7-3) 场景

在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用Proxy模式。下面是一些可以使用Proxy模式常见情况:

  1. 远程代理(Remote Proxy)为一个对象在不同的地址空间提供局部代表。

  2. 虚代理(Virtual Proxy)根据需要创建开销很大的对象。

  3. 保护代理(Protection Proxy)控制对原始对象的访问。保护代理用于对象应该有不同 的访问权限的时候。

  4. 智能指引(Smart Reference)取代了简单的指针,它在访问对象时执行一些附加操作。 它的典型用途包括:

    • 对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它(也称为SmartPointers)。
    • 当第一次引用一个持久对象时,将它装入内存。
    • 在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。

2-7-4) 总结

Proxy 模式最大的好处就是实现了逻辑和实现的彻底解耦。
Proxy 模式是增加访问控制,典型Spring,AOC。
Decorator 模式是增强子类。

3) 行为型模式

3-1) Template 模式

3-1-1) 定义

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

3-1-2) 详解

一个通用的 Template 模式的结构图为:
23设计模式总结

  • AbstractClass(抽象类,如Application)

    • 定义抽象的原语操作(primitive operation),具体的子类将重定义它们以实现一个算法的各步骤。
    • 实现一个模板方法 ,定义一个算法的骨架。该模板方法不仅调用原语操作,也调用定义 在AbstractClass或其他对象中的操作。
  • ConcreteClass(具体类,如MyApplication)

    • 实现原语操作以完成算法中与特定子类相关的步骤

3-1-3) 场景

  • 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
  • 各子类中公共的 行为应被提取出来并集中到一个公共父类中以避免代码重复。这是Opdyke和Johnson所描述过的“重分解以一般化”的一个很好的例子。首先识别现有 代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
  • 控制子类扩展。模板方法只在特定点调用“hook”操作,这样就只允许在这些点进行扩展。

3-1-4) 总结

  • 优点:

    1. 封装不变部分,扩展可变部分
      把认为是不变部分的算法封装到父类实现,而可变部分的则可以通过继承来继续扩展。在悍马模型例子中,是不是就非常容易扩展?例如增加一个H3型号的悍马模型,很容易呀,增加一个子类,实现父类的基本方法就可以了。

    2. 提取公共部分代码,便于维护
      我们例子中刚刚走过的弯路就是最好的证明,如果我们不抽取到父类中,任由这种散乱的代码发生,想想后果是什么样子?维护人员为了修正一个缺陷,需要到处查找类似的代码!

    3. 行为由父类控制,子类实现
      基本方法是由子类实现的,因此子类可以通过扩展的方式增加相应的功能,符合开闭原则。

  • 缺点:

    1. 按照我们的设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成具体的事物属性和方法。但是模板方法模式却颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,这在复杂的项目中,会带来代码阅读的难度,而且也会让新手产生不适感。

3-2) Strategy 模式

3-2-1) 定义

定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。

3-2-2) 详解

Strategy 模式典型的结构图为:
23设计模式总结

  • Strategy(策略)

    • 定义所有支持的算法的公共接口。
      Context使用这个接口来调用某个ConcreteStrategy定 义的算法。
  • ConcreteStrategy(具体策略)

    • 以Strategy接口实现某具体算法。
  • Context(上下文)?

    • 用一个ConcreteStrategy对象来配置。
    • 维护一个对Strategy对象的引用。
    • 可定义一个接口来让Stategy访问它的数据。

3-2-3) 场景

  1. 许多相关的类仅仅是行为有异。“策略”提供了一种用多个行为中的一个行为来配置一个类的方法。
  2. 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间/时间权衡的算法。当这些变体实现为一个算法的类层次时,可以使用策略模式。
  3. 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
  4. 一个类定义了多种行为, 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

3-2-4) 总结

  • 优点:

    1. 算法可以自由切换
      这是策略模式本身定义的,只要实现抽象策略,它就成为策略家族的一个成员,通过封装角色对其进行封装,保证对外提供“可自由切换”的策略。

    2. 避免使用多重条件判断
      如果没有策略模式,我们想想看会是什么样子?一个策略家族有5个策略算法,一会要使用A策略,一会要使用B策略,怎么设计呢?使用多重的条件语句?多重条件语句不易维护,而且出错的概率大大增强。使用策略模式后,可以由其他模块决定采用何种策略,策略家族对外提供的访问接口就是封装类,简化了操作,同时避免了条件语句判断。

    3. 扩展性良好
      这甚至都不用说是它的优点,因为它太明显了。在现有的系统中增加一个策略太容易了,只要实现接口就可以了,其他都不用修改,类似于一个可反复拆卸的插件,这大大地符合了OCP原则。

  • 缺点:

    1. 策略类数量增多
      每一个策略都是一个类,复用的可能性很小,类数量增多。

    2. 所有的策略类都需要对外暴露
      上层模块必须知道有哪些策略,然后才能决定使用哪一个策略,这与迪米特法则是相违背的,我只是想使用了一个策略,我凭什么就要了解这个策略呢?那要你的封装类还有什么意义?这是原装策略模式的一个缺点,幸运的是,我们可以使用其他模式来修正这个缺陷,如工厂方法模式、代理模式或享元模式。

3-3) State 模式

3-3-1) 定义

允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。

3-3-2) 详解

State 模式将每一个分支都封装到独立的类中。State 模式典型的结构图为:
23设计模式总结

  • Context(环境角色)
    — 定义客户感兴趣的接口。
    — 维护一个ConcreteState子类的实例,这个实例定义当前状态。

  • State(抽象状态角色)
    — 定义一个接口以封装与 Context的一个特定状态相关的行为。

  • ConcreteState subclasses(具体状态角色)
    — 每一子类实现一个与Context的一个状态相关的行为。

3-3-3) 场景

  • 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
  • 一个操作中含有 庞大的多分支的条件语句,且这些分支依赖于该对象的状态。这个状态通常用一个或多个枚举常量表示。通常, 有多个操作包含这一相同的条件结构。State模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一 对象可以不依赖于其他对象而独立变化。

3-3-4) 总结

  • 优点:

    1. 结构清晰
      避免了过多的switch…case或者if…else语句的使用,避免了程序的复杂性,提高系统的可维护性。

    2. 遵循设计原则
      很好地体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就要增加子类,你要修改状态,你只修改一个子类就可以了。

    3. 封装性非常好
      这也是状态模式的基本要求,状态变换放置到类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。

  • 缺点:
    子类会太多,类膨胀。

  • 注意事项:
    状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说在行为受状态约束的情况下可以使用状态模式,而且使用时对象的状态最好不要超过5个。

3-4) Observer 模式

3-4-1) 定义

观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知并被自动更新。

3-4-1) 详解

Observer 模式典型的结构图为:
23设计模式总结

参与者

  • Subject(目标)
    — 目标知道它的观察者。可以有任意多个观察者观察同一个目标。
    — 提供注册和删除观察者对象的接口。 ? Observer(观察者)
    — 为那些在目标发生改变时需获得通知的对象定义一个更新接口。

  • ConcreteSubject(具体目标)
    — 将有关状态存入各ConcreteObserver对象。
    — 当它的状态发生改变时 , 向它的各个观察者发出通知。

  • ConcreteObserver(具体观察者)
    — 维护一个指向ConcreteSubject对象的引用。— 存储有关状态,这些状态应与目标的状态保持一致。— 实现Observer的更新接口以使自身状态与目标的状态保持一致。

3-4-3) 场景

  • 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  • 当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
  • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,你不希望这些对象是紧密耦合的。

3-4-4) 总结

Observer 是影响极为深远的模式之一,也是在大型系统开发过程中要用到的模式之一。 除了 MFC、Struts 提供了 MVC 的实现框架,在 Java 语言中还提供了专门的接口实现 Observer 模式:通过专门的类 Observable 及 Observer 接口来实现 MVC 编程模式,其 UML 图可以表 示为:
23设计模式总结

这里的 Observer 就是观察者,Observable 则充当目标 Subject 的角色。
Observer 模式也称为发布-订阅(publish-subscribe),目标就是通知的发布者,观察者 则是通知的订阅者(接受通知)。

3-5) Memento 模式

3-5-1) 定义

备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token。

3-5-2) 详解

Memento 模式的关键就是要在不破坏封装行的前提下,捕获并保存一个类的内部 状态,这样就可以利用该保存的状态实施恢复操作。为了达到这个目标,可以在后面的实现 中看到我们采取了一定语言支持的技术。Memento 模式的典型结构图为:
23设计模式总结

  • Originator(发起人):负责创建一个备忘录Memento,用以记录当前时刻自身的内部状态,并可使用备忘录恢复内部状态。Originator可以根据需要决定Memento存储自己的哪些内部状态。

  • Memento(备忘录):负责存储Originator对象的内部状态,并可以防止Originator以外 的其他对象访问备忘录。备忘录有两个接口:Caretaker只能看到备忘录的窄接口,他只能将备忘录传递给其他对象。Originator却可看到备忘 录的宽接口,允许它访问返回到先前状态所需要的所有数据。

  • Caretaker(管理者):负责备忘录Memento,不能对Memento的内容进行访问或者操作。

3-5-3) 场景

  • 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。
  • 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性

3-5-4) 总结

  • 优点:

    1. 有时一些发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须要由发起人对象自己读取,这时,
      使用备忘录模式可以把复杂的发起人内部信息对其他的对象屏蔽起来,从而可以恰当地保持封装的边界。

    2. 本模式简化了发起人类。发起人不再需要管理和保存其内部状态的一个个版本,客户端可以自行管理他们所需
      要的这些状态的版本。

  • 缺点:

    1. 如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上面备忘录对象会很昂贵。

    2. 当负责人角色将一个备忘录 存储起来的时候,负责人可能并不知道这个状态会占用多大的存储空间,从而无法提醒用户一个操作是否很昂贵。

    3. 当发起人角色的状态改变的时候,有可能这个协议无效。如果状态改变的成功率不高的话,不如采取“假如”协议模式。

3-6) Mediator 模式

3-6-1) 定义

使用一个中介的对象,封装一组对象之间的交互,这样这些对象就可以不用彼此耦合。

这个中介者常常起着中间桥梁的作用,使其他的对象可以利用中介者完成某些行为活动,因此它必须对所有的参与活动的对象了如指掌!

3-6-2) 详解

Mediator 模式典型的结构图为:
23设计模式总结

  • Mediator(中介者)
    中介者定义一个接口用于与各同事( Colleague)对象通信。

  • ConcreteMediator(具体中介者)
    具体中介者通过协调各同事对象实现协作行为。
    了解并维护它的各个同事。

  • Colleague class(同事类)
    每一个同事类都知道它的中介者对象。
    每一个同事对象在需与其他的同事通信的时候,与它的中介者通信。

3-6-3) 场景

  • 一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
  • 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
  • 想定制一个分布在多个类中的行为,而又不想生成太多的子类。

3-6-4) 总结

  • 优点:
    中介者模式的优点就是减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合。

  • 缺点:
    中介者模式的缺点就是中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂。

3-7) Command 模式

3-7-1) 定义

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

3-7-1) 详解

Command 模式的典型结构图为:
23设计模式总结
* Command:
定义命令的接口,声明执行的方法。

  • ConcreteCommand:
    命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。

  • Receiver:
    接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。

  • Invoker:
    要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。

  • Client:
    创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。

3-7-3) 场景

  1. 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
  2. 系统需要在不同的时间指定请求、将请求排队和执行请求。
  3. 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
  4. 系统需要将一组操作组合在一起,即支持宏命令。

3-7-4) 总结

  • 优点:

    • 降低系统的耦合度
    • 新的命令可以很容易地加入到系统中
    • 可以比较容易地设计一个组合命令
    • 调用同一方法实现不同的功能
  • 缺点:

    • 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。

3-8) Visitor 模式

3-8-1) 定义

表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

3-8-2) 详解

我们通过 Visitor 模式解决上面的问题,其典型的结构图为:
23设计模式总结
Visitor 模式在不破坏类的前提下,为类提供增加新的新操作。Visitor 模式的关键是双分
派(Double-Dispatch)的技术【注释 1】。C++语言支持的是单分派。
在 Visitor 模式中 Accept()操作是一个双分派的操作。具体调用哪一个具体的 Accept
()操作,有两个决定因素:1)Element 的类型。因为 Accept()是多态的操作,需要具体的 Element 类型的子类才可以决定到底调用哪一个 Accept()实现;2)Visitor 的类型。 Accept()操作有一个参数(Visitor* vis),要决定了实际传进来的Visitor的实际类别才可 以决定具体是调用哪个 VisitConcrete()实现。

3-8-3) 场景

  • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。

  • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。

  • 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。

3-8-4) 总结

在这种情境下你一定要考虑使用访问者模式:

  • 业务规则要求遍历多个不同的对象。这本身也是访问者模式出发点,迭代器模式只能访问同类或同接口的数据(当然了,如果你使用instanceof,那么能访问所有的数据,这没有争论),而访问者模式是对迭代器模式的扩充,可以遍历不同的对象,然后执行不同的操作,也就是针对访问的对象不同,执行不同的操作。

Visitor 模式看似比较复杂,简单概括就是:

  • Visitor 是对数据处理算法的封装
  • Element 是对数据结构的封装
  • 本来可以单项依赖,就是visitor.visit(xxxElement), 但是为了减少Visitor中if的判断,把所有的if拆分到到Element中自己确认调用方法也就是在accept(vistor)方法中执行visitor.visit(xxxElement),所有就形成了双向依赖Element->Visitor

优点:

  1. 数据(Element)和算法(Visitor)分离
  2. 便于算法(Visitor)的添加,修改

缺点:

  1. 数据(Element)添加比较麻烦,会影响到Visitor,需要在每个Visitor中添加visitXXXElement方法

3-9) Chain of Responsibility 模式

3-9-1) 定义

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

3-9-2) 详解

Chain of Responsibility 模式典型的结构图为:
23设计模式总结

  • Handler
    定义一个处理请求的接口。
    (可选) 实现后继链。

  • ConcreteHandler
    处理它所负责的请求。
    可访问它的后继者。
    如果可处理该请求,就处理之;否则将该请求转发给它的后继者。

  • Client
    向链上的具体处理者(ConcreteHandler)对象提交请求。

3-9-3) 场景

  1. 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
  2. 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  3. 可处理一个请求的对象集合应被动态指定。

3-9-4) 总结

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

3-10) Iterator 模式

3-10-1) 定义

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

3-10-2) 详解

Iterator 模式典型的结构图为:
23设计模式总结

  • Iterator(迭代器)
    迭代器定义访问和遍历元素的接口。

  • ConcreteIterator (具体迭代器)
    具体迭代器实现迭代器接口。
    对该聚合遍历时跟踪当前位置。

  • Aggregate (聚合)
    聚合定义创建相应迭代器对象的接口。

  • ConcreteAggregate (具体聚合)
    具体聚合实现创建相应迭代器的接口,该操作返回ConcreteIterator的一个适当的实例。

3-10-3) 场景

  1. 访问一个聚合对象的内容而无需暴露它的内部表示。
  2. 支持对聚合对象的多种遍历。
  3. 为遍历不同的聚合结构提供一个统一的接口(即, 支持多态迭代)。

3-10-4) 总结

Iterator 模式的应用很常见,我们在开发中就经常会用到 STL 中预定义好的 Iterator 来对 STL 类进行遍历(Vector、Set 等)

3-11) Interpreter 模式

3-11-1) 定义

给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中句子。

3-11-2) 详解

Interpreter 模式典型的结构图为:
23设计模式总结

模式所涉及的角色如下所示:

  1. 抽象表达式(Expression)角色:声明一个所有的具体表达式角色都需要实现的抽象接口。这个接口主要是一个interpret()方法,称做解释操作。
  2. 终结符表达式(Terminal Expression)角色:实现了抽象表达式角色所要求的接口,主要是一个interpret()方法;文法中的每一个终结符都有一个具体终结表达式与之相对应。比如有一个简单的公式R=R1+R2,在里面R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。
  3. 非终结符表达式(Nonterminal Expression)角色:文法中的每一条规则都需要一个具体的非终结符表达式,非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中,“+”就是非终结符,解析“+”的解释器就是一个非终结符表达式。
  4. 环境(Context)角色:这个角色的任务一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,我们给R1赋值100,给R2赋值200。这些信息需要存放到环境角色中,很多情况下我们使用Map来充当环境角色就足够了。

3-11-3) 适用性

当有一个语言需要解释执行 , 并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:

  • 该文法简单对于复杂的文法 , 文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式 , 这样可以节 省空间而且还可能节省时间。

  • 效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的 , 而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种 情况下, 转换器仍可用解释器模式实现, 该模式仍是有用的。

3-11-4) 总结

Interpreter 模式则提供了一种很好的组织和设计这种解析器的架构。
Interpreter 模式中使用类来表示文法规则,因此可以很容易实现文法的扩展。另外对于 终结符我们可以使用 Flyweight 模式来实现终结符的共享。

  • 优点:

    1. 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
    2. 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
    3. 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。
    4. 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”。
  • 缺点:

    1. 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
    2. 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。