设计模式精简图册

Posted 渡口一艘船

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式精简图册相关的知识,希望对你有一定的参考价值。

设计模式精简图册

首发于我的公众号

设计模式图册

设计模式分类

创建型模式:

主要用于创建对象,包括

  • 工厂方法(Factory Method)
  • 抽象工厂(Abstract Factory)
  • 单例(Singleton)
  • 生成器(Builder)
  • 原型(Prototype)

结构型模式:

用于处理类或者对象的组合,包括

  • 适配器(Adapter)
  • 装饰者(Decorator)
  • 代理(Proxy)
  • 外观(Facade)
  • 桥接(Bridge Pattern)
  • 组合(Composite)
  • 轻量(Flyweigh)

行为型模式:

用于描述类与对象怎样的交互和分配职责,包括

  • 策略(Strategy)
  • 观察者(Observer)
  • 命令(Command)
  • 模板方法(Template Method)
  • 迭代器(Iterator)
  • 状态(State)
  • 责任链(Chain)
  • 解释器(Interpreter)
  • 中介者(Mediator)
  • 备忘录(Memo)
  • 访问者(Visitor)

设计原则

单一职责原则(Single responsibility principle)

  • 核心
    不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
  • 问题产生
    类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。
  • 解决方案
    遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责P2功能。这样,当修改类T1时,不会使职责P2发生故障风险;同理,当修改T2时,也不会使职责P1发生故障风险。

但是由于职责扩散会导致在实际中往往会有悖于单一职责

里氏代换原则(Liskov Substitution Principle LSP)

  • 核心
    所有引用基类的地方必须能透明地使用其子类的对象。
  • 问题产生
    有一功能P1,由类A完成。现需要将功能P1进行扩展,扩展后的功能为P,其中P由原有功能P1与新功能P2组成。新功能P由类A的子类B来完成,则子类B在完成新功能P2的同时,有可能会导致原有功能P1发生故障。
  • 解决方案
    当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,也尽量不要重载父类A的方法。

继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。

继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。

接口隔离原则(Interface Segregation Principle)

  • 核心
    不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
  • 问题产生
    类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D必须去实现他们不需要的方法。
  • 解决方案
    将臃肿的接口I拆分为独立的几个接口,类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则。

接口隔离原则跟之前的单一职责原则很相似,其实不然。其一,单一职责原则原注重的是职责;而接口隔离原则注重对接口依赖的隔离。其二,单一职责原则主要是约束类,其次才是接口和方法,它针对的是程序中的实现和细节;而接口隔离原则主要约束接口接口,主要针对抽象,针对程序整体框架的构建。

采用接口隔离原则对接口进行约束时,要注意以下几点:

  • 接口尽量小,但是要有限度。对接口进行细化可以提高程序设计灵活性是不挣的事实,但是如果过小,则会造成接口数量过多,使设计复杂化。所以一定要适度。
  • 为依赖接口的类定制服务,只暴露给调用的类它需要的方法,它不需要的方法则隐藏起来。只有专注地为一个模块提供定制服务,才能建立最小的依赖关系。
  • 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

运用接口隔离原则,一定要适度,接口设计的过大或过小都不好。设计接口的时候,只有多花些时间去思考和筹划,才能准确地实践这一原则。

迪米特原则(Law of Demeter/Least Knowledge Principle )

  • 核心
    迪米特法则又叫最少知道原则,一个对象应该对其他对象保持最少的了解。
  • 问题产生
    类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
  • 解决方案
    尽量降低类与类之间的耦合。

通俗的来讲,就是一个类对自己依赖的类知道的越少越好。也就是说,对于被依赖的类来说,无论逻辑多么复杂,都尽量地的将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息,
迪米特法则还有一个更简单的定义:只与直接的朋友通信。首先来解释一下什么是直接的朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现成员变量、方法参数、方法返回值中的类为直接的朋友,而出现在局部变量中的类则不是直接的朋友。也就是说,陌生的类最好不要作为局部变量的形式出现在类的内部。

依赖倒置原则(Dependence Inversion Principle)

  • 核心
    高层模块不应该依赖低层模块,二者都应该依赖其抽象;抽象不应该依赖细节;细节应该依赖抽象。
  • 问题产生
    类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。
  • 解决方案
    将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。

依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

依赖倒置原则的核心思想是面向接口编程,

开闭原则(Open Close Principle)

  • 核心
    一个软件实体如类、模块和函数应该对扩展开放,对修改关闭。
  • 问题产生
    在软件的生命周期内,因为变化、升级和维护等原因需要对软件原有代码进行修改时,可能会给旧代码中引入错误,也可能会使我们不得不对整个功能进行重构,并且需要原有代码经过重新测试。
  • 解决方案
    当软件需要变化时,尽量通过扩展软件实体的行为来实现变化,而不是通过修改已有的代码来实现变化。

开闭原则无非就是想表达这样一层意思:用抽象构建框架,用实现扩展细节。因为抽象灵活性好,适应性广,只要抽象的合理,可以基本保持软件架构的稳定。而软件中易变的细节,我们用从抽象派生的实现类来进行扩展,当软件需要发生变化时,我们只需要根据需求重新派生一个实现类来扩展就可以了。当然前提是我们的抽象要合理,要对需求的变更有前瞻性和预见性才行。

几个原则的关联性

用抽象构建框架,用实现扩展细节的注意事项而已:

  • 单一职责原则告诉我们实现类要职责单一;
  • 里氏替换原则告诉我们不要破坏继承体系;
  • 依赖倒置原则告诉我们要面向接口编程;
  • 接口隔离原则告诉我们在设计接口的时候要精简单一;
  • 迪米特法则告诉我们要降低耦合。
    而开闭原则是总纲,他告诉我们要对扩展开放,对修改关闭。

创建型设计模式(创建对象)

工厂方法(Factory Method Pattern)

名称 Factory Method
结构
动机 定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。
适用性
  • 当一个类不知道它所必须创建的对象的类的时候。
  • 当一个类希望由它的子类来指定它所创建的对象的时候。
  • 当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
优点
  • 在工厂方法中,用户只需要知道所要产品的具体工厂,无须关系具体的创建过程,甚至不需要具体产品类的类名。
  • 在系统增加新的产品时,我们只需要添加一个具体产品类和对应的实现工厂,无需对原工厂进行任何修改,很好地符合了“开闭原则”。
缺点
  • 每次增加一个产品时,都需要增加一个具体类和对象实现工厂,是的系统中类的个数成倍增加,在一定程度上增加了系统的复杂度,同时也增加了系统具体类的依赖。这并不是什么好事。
小结
  • 简单工厂模式的要点就在于当你需要什么,只需要传入一个正确的参数,就可以获取你所需要的对象,而无须知道其创建细节。
  • 简单工厂模式最大的优点在于实现对象的创建和对象的使用分离,但是如果产品过多时,会导致工厂代码非常复杂。

抽象工厂(Abstract Factory Pattern)

名称 Abstract Factory
结构
动机 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
适用性
  • 一个系统要独立于它的产品的创建、组合和表示时。
  • 一个系统要由多个产品系列中的一个来配置时。
  • 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
  • 当你提供一个产品类库,而只想显示它们的接口而不是实现时。
优点
  • 抽象工厂隔离了具体类的生成,是的客户端不需要知道什么被创建。所有的具体工厂都实现了抽象工厂中定义的公共接口,因此只需要改变具体工厂的实例,就可以在某种程度上改变整个软件系统的行为。
  • 当一个产品族中的多个对象被设计成一起工作时,它能够保证客户端始终只使用同一个产品族中的对象。
缺点
  • 添加新的行为时比较麻烦。如果需要添加一个新产品族对象时,需要更改接口及其下所有子类,这必然会带来很大的麻烦。
小结
  • 抽象工厂模式中主要的优点在于具体类的隔离,是的客户端不需要知道什么被创建了。其缺点在于增加新的等级产品结构比较复杂,需要修改接口及其所有子类。

生成器/建造者模式(Builder Pattern)

名称 Builder
结构
动机 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
适用性
  • 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
  • 当构造过程必须允许被构造的对象有不同的表示时。
优点
  • 将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,使得我们能够更加精确的控制复杂对象的产生过程。
  • 将产品的创建过程与产品本身分离开来,可以使用相同的创建过程来得到不同的产品。也就说细节依赖抽象。
  • 每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。
缺点
  • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  • 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。
小结
  • 建造者模式是将一个复杂对象的创建过程给封装起来,客户只需要知道可以利用对象名或者类型就能够得到一个完整的对象实例,而不需要关心对象的具体创建过程。
  • 建造者模式将对象的创建过程与对象本身隔离开了,使得细节依赖于抽象,符合依赖倒置原则。可以使用相同的创建过程来创建不同的产品对象。

原型模式(Prototype Pattern)

名称 Prototype
结构
动机 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
适用性
  • 当要实例化的类是在运行时刻指定时,例如,通过动态装载;或者
  • 为了避免创建一个与产品类层次平行的工厂类层次时;或者
  • 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。
优点
  • 如果创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
  • 可以使用深克隆保持对象的状态。
  • 原型模式提供了简化的创建结构。
缺点
  • 在实现深克隆的时候可能需要比较复杂的代码。
  • 需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了“开闭原则”。
小结
  • 原型模式向客户隐藏了创建对象的复杂性。客户只需要知道要创建对象的类型,然后通过请求就可以获得和该对象一模一样的新对象,无须知道具体的创建过程。
  • 克隆分为浅克隆和深克隆两种。
  • 我们虽然可以利用原型模式来获得一个新对象,但有时对象的复制可能会相当的复杂,比如深克隆。

单例模式(Singleton Pattern)

名称 Singleton
结构
动机 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
适用性
  • 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时。
  • 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。
优点
  • 节约了系统资源。由于系统中只存在一个实例对象,对与一些需要频繁创建和销毁对象的系统而言,单 例模式无疑节约了系统资源和提高了系统的性能。
  • 因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
缺点
  • 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
  • 单例类的职责过重,在一定程度上违背了“单一职责原则”。
小结
  • 单例模式中确保程序中一个类最多只有一个实例。
  • 单例模式的构造器是私有了,而且它必须要提供实例的全局访问点。
  • 单例模式可能会因为多线程的问题而带来安全隐患。
public class BetterSingleton 

    private BetterSingleton2() 
    

    public static BetterSingleton getInstance() 
        return Singleton.BETTER_SINGLETON;
    

    publicstatic class Singleton
      private static final  BetterSingleton BETTER_SINGLETON = new BetterSingleton();
    

创建型设计模式小结

模式场景发散一句话概括
工厂方法(Factory Method)new太多如何管理生产系列产品。
抽象工厂(Abstract Factory)new太多如何管理一次生产多个不同产品。
生成器(Builder)车手选车生产有很多组件的产品。
原型(Prototype)复制不能很难克隆对象。
单件(Singleton)如何管理全局信息全局只有一个。

结构型设计模式(处理类或者对象的组合)

桥接模式(Bridge Pattern)

名称 Bridge
结构
动机 将抽象部分与它的实现部分分离,使它们都可以独立地变化。
适用性
  • 如果一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性,避免在两个层次之间建立静态的继承联系,通过桥接模式可以使它们在抽象层建立一个关联关系。
  • 对于那些不希望使用继承或因为多层次继承导致系统类的个数急剧增加的系统,桥接模式尤为适用
  • 一个类存在两个独立变化的维度,且这两个维度都需要进行扩展。
优点
  • 分离抽象接口及其实现部分。提高了比继承更好的解决方案。
  • 桥接模式提高了系统的可扩充性,在两个变化维度中任意扩展一个维度,都不需要修改原有系统。
  • 实现细节对客户透明,可以对用户隐藏实现细节。
缺点
  • 桥接模式的引入会增加系统的理解与设计难度,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计与编程。
  • 桥接模式要求正确识别出系统中两个独立变化的维度,因此其使用范围具有一定的局限性。
小结
  • 桥接模式实现了抽象化与实现化的脱耦。他们两个互相独立,不会影响到对方。
  • 对于两个独立变化的维度,使用桥接模式再适合不过了。
  • 对于“具体的抽象类”所做的改变,是不会影响到客户。

轻量模式/享元模式(FlyWeightPattern)

名称 Flyweight
结构
动机 运用共享技术有效地支持大量细粒度的对象。
适用性
  • 一个应用程序使用了大量的对象。
  • 完全由于使用大量的对象,造成很大的存储开销。
  • 对象的大多数状态都可变为外部状态。
  • 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
  • 应用程序不依赖于对象标识。由于F l y w e i g h t 对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值。
优点
  • 享元模式的优点在于它能够极大的减少系统中对象的个数。
  • 享元模式由于使用了外部状态,外部状态相对独立,不会影响到内部状态,所以享元模式使得享元对象能够在不同的环境被共享。
缺点
  • 由于享元模式需要区分外部状态和内部状态,使得应用程序在某种程度上来说更加复杂化了。
  • 为了使对象可以共享,享元模式需要将享元对象的状态外部化,而读取外部状态使得运行时间变长。
小结
  • 享元模式可以极大地减少系统中对象的数量。但是它可能会引起系统的逻辑更加复杂化。
  • 享元模式的核心在于享元工厂,它主要用来确保合理地共享享元对象。
  • 内部状态为不变共享部分,存储于享元享元对象内部,而外部状态是可变部分,它应当由客户端来负责。

外观模式(Facade Pattern)

名称 Facade
结构
动机 为子系统中的一组接口提供一个一致的界面,F a c a d e 模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
适用性
  • 当你要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂。大多数模式使用时都会产生更多更小的类。这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。F a c a d e 可以提供一个简单的缺省视图,这一视图对大多数用户来说已经足够,而那些需要更多的可定制性的用户可以越过f a c a d e 层。
  • 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入f a c a d e 将这个子系统与客户以及其他的子系统分离,可以提高子系统的独立性和可移植性。
  • 当你需要构建一个层次结构的子系统时,使用f a c a d e 模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,你可以让它们仅通过f a c a d e 进行通讯,从而简化了它们之间的依赖关系。
优点
  • 引入外观模式,是客户对子系统的使用变得简单了,减少了与子系统的关联对象,实现了子系统与客户之间的松耦合关系。
  • 只是提供了一个访问子系统的统一入口,并不影响用户直接使用子系统类
  • 降低了大型软件系统中的编译依赖性,并简化了系统在不同平台之间的移植过程
缺点
  • 不能很好地限制客户使用子系统类,如果对客户访问子系统类做太多的限制则减少了可变性和灵活性
  • 在不引入抽象外观类的情况下,增加新的子系统可能需要修改外观类或客户端的源代码,违背了“开闭原则”
小结
  • 外观模式的主要优点就在于减少了客户与子系统之间的关联对象,使用客户对子系统的使用变得简单了,也实现了客户端与子系统之间的松耦合关系。它的缺点就在于违背了“开闭原则”。
  • 如果需要实现一个外观模式,需要将子系统组合进外观中,然后将工作委托给子系统执行。

装饰者模式(Decorator Pattern)

名称 Decorator
结构
动机 动态地给一个对象添加一些额外的职责。就增加功能来说,D e c o r a t o r 模式相比生成子类更为灵活。
适用性
  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 处理那些可以撤消的职责。
  • 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
优点
  • 装饰者模式可以提供比继承更多的灵活性
  • 可以通过一种动态的方式来扩展一个对象的功能,在运行时选择不同的装饰器,从而实现不同的行为。
  • 通过使用不同的具体装饰类以及这些装饰类的排列组合,可以创造出很多不同行为的组合。可以使用多个具体装饰类来装饰同一对象,得到功能更为强大的对象。
  • 具体构件类与具体装饰类可以独立变化,用户可以根据需要增加新的具体构件类和具体装饰类,在使用时再对其进行组合,原有代码无须改变,符合“开闭原则”。
缺点
  • 会产生很多的小对象,增加了系统的复杂性
  • 这种比继承更加灵活机动的特性,也同时意味着装饰模式比继承更加易于出错,排错也很困难,对于多次装饰的对象,调试时寻找错误可能需要逐级排查,较为烦琐。
小结
  • 装饰者模式意味着一群装饰者类,这些类用来包装具体组件
  • 装饰者可以在被装饰者的行为前面或者后面加上自己的行为,甚至可以将被装饰者的行为整个取代掉,从而达到特定的目的。
  • 可以用多个装饰者包装一个组件。
  • 装饰者一般对于组件的客户是透明的,除非客户程序依赖于组件的具体类型。
  • 装饰者会导致设计中出现许多的小对象,如果过度的使用,会让系统变得更加复杂。
  • 装饰者和被装饰者对象有相同的超类型。

组合模式(Composite Pattern)

名称 Composite
结构
动机 将对象组合成树形结构以表示?部分-整体?的层次结构。C o m p o s i t e 使得用户对单个对象和组合对象的使用具有一致性。
适用性
  • 你想表示对象的部分-整体层次结构。
  • 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
优点
  • 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。
  • 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象。
  • 定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
  • 更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码。
缺点
  • 使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联,会有冗余代码
小结
  • 组合模式用于将多个对象组合成树形结构以表示“整体-部分”的结构层次。组合模式对单个对象(叶子对象)和组合对象(容器对象)的使用具有一致性。
  • 组合对象的关键在于它定义了一个抽象构建类,它既可表示叶子对象,也可表示容器对象,客户仅仅需要针对这个抽象构建进行编程,无须知道他是叶子对象还是容器对象,都是一致对待。
  • 组合模式虽然能够非常好地处理层次结构,也使得客户端程序变得简单,但是它也使得设计变得更加抽象,而且也很难对容器中的构件类型进行限制,这会导致在增加新的构件时会产生一些问题。

代理模式(Proxy Pattern)

名称 Proxy
结构
动机 为其他对象提供一种代理以控制对这个对象的访问。
适用性
  • 不希望某些类被直接访问。
  • 访问之前希望先进行一些预处理。
  • 希望对被访问的对象进行内存、权限等方面的控制。 模式。
优点
  • 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
  • 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了的作用和保护了目标对象的
缺点
  • 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
  • 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
小结
  • 代理模式是通过使用引用代理对象来访问真实对象,在这里代理对象充当用于连接客户端和真实对象的中介者。
  • 代理模式主要用于远程代理、虚拟代理和保护代理。其中保护代理可以进行访问权限控制。

适配器模式(Adapter Pattern)

名称 Adapter
结构
        <div data-type="image" data-display="block" data-align="center" data-src="http://www.uml.org.cn/chanpin/intro/WebHelp/Adapter_Class.gif" data-width="543">
          <img src="http://www.uml.org.cn/chanpin/intro/WebHelp/Adapter_Class.gif" width="543" />
        </div>
      </div>
    </td>
  </tr>
  <tr>
    <td rowspan="1" colSpan="1">
      <div data-type="p">动机</div>
    </td>
    <td rowspan="1" colSpan="1">
      <div data-type="p">将一个类的接口转换成客户希望的另外一个接口。Adapter 模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。</div>
    </td>
  </tr>
  <tr>
    <td rowspan="1" colSpan="1">
      <div data-type="p">适用性</div>
    </td>
    <td rowspan="1" colSpan="1">
      <ul data-type="unordered-list">
        <li data-type="list-item" data-list-type="unordered-list">
          <div data-type="p">你想使用一个已经存在的类,而它的接口不符合你的需求。</div>
        </li>
        <li data-type="list-item" data-list-type="unordered-list">
          <div data-type="p">你想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作。</div>
        </li>
        <li data-type="list-item" data-list-type="unordered-list">
          <div data-type="p">(仅适用于对象Adapter )你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以适配它的父类接口。</div>
        </li>
      </ul>
    </td>
  </tr>
  <tr height="34px">
    <td rowspan="1" colSpan="1">
      <div data-type="p">优点</div>
    </td>
    <td rowspan="1" colSpan="1">
      <ul data-type="unordered-list">
        <li data-type="list-item" data-list-type="unordered-list">
          <div data-type="p">将目标类和适配者类解耦,通过使用适配器让不兼容的接口变成了兼容,让客户从实现的接口解耦。</div>
        </li>
        <li data-type="list-item" data-list-type="unordered-list">
          <div data-type="p">增加了类的透明性和复用性,将具体的实现封装在适配者类中,对于客户端类来说是透明的,而且提高了适配者的复用性。</div>
        </li>
        <li data-type="list-item" data-list-type="unordered-list">
          <div data-type="p">灵活性和扩展性都非常好在不修改原有代码的基础上增加新的适配器类,符合“开闭原则”。</div>
        </li>
      </ul>
    </td>
  </tr>
  <tr height="34px">
    <td rowspan="1" colSpan="1">
      <div data-type="p">缺点</div>
    </td>
    <td rowspan="1" colSpan="1">
      <ul data-type="unordered-list">
        <li data-type="list-item" data-list-type="unordered-list">
          <div data-type="p">过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。</div>
        </li>
        <li data-type="list-item" data-list-type="unordered-list">
          <div data-type="p">因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。</div>
        </li>
      </ul>
    </td>
  </tr>
  <tr height="34px">
    <td rowspan="1" colSpan="1">
      <div data-type="p">小结</div>
    </td>
    <td rowspan="1" colSpan="1">
      <ul data-type="unordered-list">
        <li data-type="list-item" data-list-type="unordered-list">
          <div data-type="p">当我们需要使用的一个现有的类,但是他的接口并不符合我们的需求时,我们可以使用适配器模式。</div>
        </li>
        <li data-type="list-item" data-list-type="unordered-list">
          <div data-type="p">适配器模式分为类适配器和对象适配器,其中类适配器需要用到多重继承。</div>
        </li>
      </ul>
    </td>
  </tr>
</tbody>

结构型设计模式小结

模式场景发散一句话说明
桥(Bridge)麻烦的日志记录将“抽象”和“实现”自由搭配。
轻量(Flyweight)森林里的树太多了轻松地处理“大量”对象。
外观(Façade)超级手机同时提供简单接口和复杂接口。
装饰者(Decorator)星巴克的饮料计较系统不改变接口但要增强功能。
组合(Composite)超酷的绘图软件不管你是老子还是儿子,都一样处理。
代理(Proxy)找中介租房代理要控制你的访问,同时让你的访问更舒服 。
适配器(Adapter)老掉牙系统的重生不改变功能但要改变接口

行为型设计模式(类与对象怎样的交互和分配职责)

观察者模式(Observer Pattern)

名称 Observer
结构
动机 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新。
适用性
  • 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  • 当对一个对象的改变需要同时改变其它对象, 而不知道具体有多少对象有待改变。
  • 当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之, 你不希望这些对象是紧密耦合的。
优点
  • 当两个对象之间送耦合,他们依然可以交互,但是不太清楚彼此的细节。观察者模式提供了一种对象设计,让主题和观察者之间送耦合。主题所知道只是一个具体的观察者列表,每一个具体观察者都符合一个抽象观察者的接口。主题并不认识任何一个具体的观察者,它只知道他们都有一个共同的接口。
  • 观察者模式支持“广播通信”。主题会向所有的观察者发出通知。
  • 观察者模式符合“开闭原则”的要求。
缺点
  • 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进 行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
小结
  • 观察者模式定义了对象之间的一对多关系。多个观察者监听同一个被观察者,当该被观察者的状态发生改变时,会通知所有的观察者。
  • 观察者模式中包含四个角色。主题,它指被观察的对象。具体主题是主题子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;观察者,将对观察主题的改变做出反应;具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致。

策略模式(Strategy Pattern)

名称 Strategy
结构
动机 定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。
适用性
  • 许多相关的类仅仅是行为有异。策略模式提供了一种用多个行为中的一个行为来配置一个类的方法。
  • 需要使用一个算法的不同变体。
  • 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
  • 一个类定义了多种行为, 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的S t r a t e g y 类中以代替这些条件语句。
优点
  • 策略模式提供了对“开闭原则”的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
  • 策略模式提供了可以替换继承关系的办法。
  • 使用策略模式可以避免使用多重条件转移语句。
缺点
  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
  • 策略模式将造成产生很多策略类,
小结
  • 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
  • 一个系统需要动态地在几种算法中选择一种。
  • 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

迭代器模式(Iterator Pattern)

名称 Iterator
结构
动机 提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。
适用性
  • 访问一个聚合对象的内容而无需暴露它的内部表示。
  • 支持对聚合对象的多种遍历。
  • 为遍历不同的聚合结构提供一个统一的接口(即, 支持多态迭代)。
优点
  • 它支持以不同的方式遍历一个聚合对象。
  • 迭代器简化了聚合类。
  • 在同一个聚合上可以有多个遍历。
  • 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点
  • 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
小结