设计模式:目录

设计模式介绍

  • 设计模式是构造OO系统的隐含经验,是为了设计出弹性的、可复用的、可维护的系统
  • 建立可维护的系统,在于随时思考系统之后可能需要的变化以及应付变化的原则
  • 当找不到合适的设计模式,则思考一下面向对象的原则,例如:抽象、封装这些很基础的东西

常用设计模式:

  • 单例模式
  • 策略模式
  • 代理模式
  • 观察者模式
  • 装饰模式
  • 适配器模式
  • 命令模式
  • 工厂模式
  • 模板方法模式
  • 建造者模式
  • 桥接模式

模式的重要性

  • 知道OO基础,并不足以设计出良好的OO系统
  • 良好的OO设计必须具备可复用、可维护、可扩充的特性
  • 模式可以让我们建造出具有良好的OO设计质量的系统
  • 模式可以认为是历经验证的OO设计经验
  • 模式不是代码,而是针对设计问题的通用解决方案,可把他们应用到特定的应用中
  • 大多数的模式和原则,都着眼于软件变化的主题
  • 大多数模式都允许系统局部改变独立于其他部分

设计模式类目

根据两条准则对模式进行分类:

  • 目的准则,即模式用来完成什么工作
    • 创建型模式与对象创建有关
    • 结构型模式处理类或对象的组合
    • 行为型模式对类或对象怎么样交互和怎样分配职责进行描述
  • 范围准则,指定模式主要是用于类还是用于对象。
    • 类模式处理类和子类间的关系,通过继承建立,是静态的,编译期确定
    • 对象模式处理对象间关系,是动态的。

1562807738415

创建型

工厂模式Factory

定义:定义一个创造对象的接口,但由子类决定要实例化的类是哪一个。工厂方法让类把实例化推迟到子类

  • 当面对算法时常改动时,每次维护与扩展都需要对工厂进行修改。此时使用工厂模式并不合适,应当选用策略模式

适用性

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

Abstract Factory

提供一个创建一系列相关或相互依赖的接口,而无需指定它们具体的类

适用性

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

单例模式Singleton

定义:确保一个类只有一个实例,并提供全局访问

适用性

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

建造模式Builder

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

适用性

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

原型模式Prototype

原型模式:用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象

适用性

当一个系统应该独立于它的产品创建、构成和表示时

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

行为型

概述

行为型模式设计到算法和对象间职责的分配

行为模式不仅描述对象或类的模式,还描述它们间的通信模式,这些模式刻划了在运行时难以跟踪的复杂的控制流,将你的注意力从控制流转移到对象间的联系方式上。

  • 行为类模式使用继承机制在类间分派行为。
    • template method是一个算法的抽象定义,逐步定义该算法,每一步调用抽象成一个原语操作,子类具体实现抽象以实现该算法
    • Interpreter将一个文法表示为一个类层次,并实现一个解释器作为这些类的实例上的一个操作
  • 行为对象模式使用对象复合而不是继承。
    • 一些行为对象模式描述了一组对等的对象怎样相互协作以完成其中任一个对象都无法单独完成的任务。其问题是对等的对象如何相互了解对象,对等对象可以保持显式对对方的引用,但会增加耦合度,极端情况下,每个对象都要了解所有其他的对象
    • Mediator在对等对象间引入一个Mediator对象以避免这种情况,提供了松耦合所需要的间接性
    • Chain of Responsibility提供更松的耦合,让你通过一条候选对象链隐式的向一个对发送请求,根据运行时刻情况任一候选者都可以响应相应的请求。候选者数目是任意的,你可以在运行时刻决定哪些候选者参与链中
    • Observer模式定义并保持对象间的依赖关系,典型是MVC模式,一旦模型状态改变,模型中所有视图将得到通知
    • 其他的行为对象模式常将行为封装在一个对象中并将请求指派给它
      • Strategy模式将算法封装到对象中,可以方便指定和改变一个对象所用的算法
      • Command模式将请求封装在对象中,这样它就可以作为参数来传递,也可以被存储在历史列表中,或以其他方式使用
      • State模式封装一个对象的这套,使得当这个对象的状态对象变化时,该对象可以改变它的行为
      • Visitor封装分布于多个类间的行为
      • Iterator抽象了访问和遍历一个集合中对象的方式

命令模式Command

定义:将请求封装成对象,这可以让你使用不同的请求、队列、或者日志请求来参数化其他对象。命令模式也支持撤销操作

适用性

  • 抽象出待执行动作以参数化对象。可用过程语言中的回调函数表达该参数化机制
  • 在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另一个不同的进程并在那实现请求。
  • 支持取消操作。Execute操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响,并可实现重数的取消
  • 支持修改日志
  • 用构建在原语操作上的高层操作构造一个系统。这种结构在支持事务的信息系统中很场景

迭代器模式Iterator

迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示

适用性

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

观察者模式Observer

定义:在对象间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知并自动更新

适用性

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

策略模式Strategy

定义:定义了算法族,分别分装起来,让他们可以互相替换,此模式使得算法的变化独立于使用算法的客户

为多种算法创建一个统一的接口,客户持有一个接口引用,可以自由地切换算法类。

在实现上,客户端认识的类更少,减少了各种算法类与使用算法类之间的耦合

举例

一个人自由切换它的出行方式(自行车、汽车)。因此就不需要去使用IF判断他的出行方式而在客户类当中书写相应代码。

进阶

策略模式是用来封装算法的,但在实践中,也可以用来封装几乎任何类型的规则,只要在分析过程中听到需要在不同的时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性。

适用性

  • 许多相关的类仅仅是行为有异。策略提供了一种用多个行为中的一个行为来配置一个类的方法
  • 需要使用一个算法的不同变体
  • 算法使用客户不应该知道的数据。策略模式避免暴露复杂、与算法相关的数据结构
  • 一个类定义了多种行为,并且这些行为在这个类d额操作中以多个条件语句的形式出现,将相关的条件分支移入它们各自的Strategy中

状态模式State

定义:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类

适用性

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

模板方法模式Template

定义:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

适用性

  • 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现
  • 各子类中的公共的行为应该被提取出来并集中到一个父类中以避免代码重复
  • 控制子类扩展,模板方法只在特定点调用“hook”操作,这样就只允许在这些点进行扩展

访问者模式Visitor

访问者模式:表示一个作用与某对象结构中的各元素操作。使你在不改变各元素的类的前提下定义作用于这些元素的新操作

适用性

  • 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作
  • 需要对一个对象结构中的对象进行很多不同且不相关的操作,而你想要避免这些操作污染这些对象的类
    • visitor使得你可以将相关的操作集中起来定义在一个类中
  • 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作,改变对象结构的类需要重新定义对所有访问者的接口,会付出很大的代价。

备忘录模式Memento

备忘录模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到保存的状态

适用性

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

责任链模式Chain of Responsibility

责任链模式:为解除请求的发送者和接受者之间的耦合,而使得多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它

适用性

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

中介者模式Mediator

中介者模式:用一个中介对象来封装一系列对象的交互,中介者使各对象不需要显式地相互引用,从而使得其耦合松散,而且可以独立地改变它们之间的交互

适用性

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

解释器模式Interpreter

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

类行为模式

适用性

当有一个语言需要解释执行,并且你可以将该语言中的句子表示为一个抽象语法树时,可以使用解释器模式

  • 该文法简单对于复杂的文法,文法的类层次变得庞大而无法管理。
    • 比如四则运算会带来非常多的表达式,公式每次不同,但都是四则四个非终极符连接
    • 有一个简单的语法规则,比如一个sql语句,如果我们需要根据sql语句进行rm转换,就可以使用解释器模式来对语句进行解释。
  • 效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种形式。例如正则表达式通常被转换为状态机,而这种转换器依然可以用解释器实现。

结构型

结构型模式设计如何组合类和对象以获得更大的结构。

  • 结构型类模式使用继承进行组合接口或实现;
  • 结构型对象模式描述了如何对一些对象进行组合,从而实现新功能的一些方法,其在运行期改变组合关系,具有更大的灵活性。

适配器模式Adapter

适配器模式:将一个类的接口,转换成客户期望的另一个接口。适配器让原本不兼容的类可以合作无间

适用性

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

桥接模式Bridge

桥接模式:将抽象部分与它的实现部分分离,使他们都可以独立地变化

适用性

  • 不希望在抽象和它的实现部分间有一个固定的绑定关系。可能是因为在程序运行时刻实现部分应可以被选择或切换
  • 类的抽象以及它的实现都应该可以通过生成子类的方法进行扩充。这时Bridge模式使你可以对不同的抽象接口和实现部分进行组合,并分别对他们进行扩充
  • 对一个抽象的实现部分的修改应该对客户不产生影响,即客户的代码不必重新编译

外观模式Facade

外观模式:提供了一个统一的接口,用来访问子系统中的一群接口。外观定义了一个高层接口,让子系统更容易使用。

适用性

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

代理模式Proxy

定义:为另一个对象提供一个替身或者占位符或代理以控制访问这个对象。

适用性

需要用比较通用和复杂的对象指针代替简单的指针时使用代理模式。

  • 远程代理。
  • 虚代理
  • 保护代理。控制对原始对象的访问,保护代理用于对象应该有不同的访问权限的时候
  • 智能指引取代了简单的指针,在访问对象时执行一些附加的操作
    • 对指向实际对象的引用计数,当对象没有用时,可以自动释放
    • 在访问一个对象时,对它进行事务、日志等操作

装饰者模式Decorator

定义:动态地将责任附加到对象上,想要扩展功能,装饰者提供有别于继承的另一种选择。

适用性

  • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
  • 处理那些可以撤销的职责。
  • 当不能采用生成子类的方法进行扩充时。
    • 如可能有大量独立的扩展,每一种组合将产生大量的子类,使得子类数目爆炸增长。
    • 可能因为类定义被隐藏或类定义不能用来生成子类。

享元模式Flyweight

享元模式:运用共享技术有效支持大量细粒度的对象

适用性

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

组合模式Composite

组合模式:允许你将对象组成树形结构来表现“整体/部分”的层次结构。组合能让客户以一致的方式处理个别对象和对象组合

适用性

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

复合模式

定义:

##

设计原则

  • 开闭原则。
  • 依赖倒置原则。
  • 单一职责原则。
  • 接口隔离原则。
  • 迪米特法则(最少知道原则)。
  • 里氏替换原则。
  • 合成/复用原则(组合/复用原则)。

追寻原则不能过度,要根据实际场景取舍。

开放封闭原则

类、模块、函数应该对扩展开放,对修改关闭。

软件实体应当对扩展开放,对修改关闭。

  • 用抽象构建框架,用实现扩展细节。
    • 面向抽象编程。
    • 抽象是关键。如果没有抽象类或接口系统就没有扩展点。
    • 面向抽象尽量不要修改原来的代码,是指不修改抽象,而抽象是稳定的,因此不容易变化。
  • 优点:提高软件系统的可复用性与可维护性。

封装可变性。将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂到一起,系统将变得复杂混乱

继承是一种 IS-A 关系,子类需要能够当成父类来使用,并且需要比父类更特殊。

如果不满足这个原则,那么各个子类的行为上就会有很大差异,增加继承体系的复杂度。

举例

依赖倒置原则

高层模块不应该依赖于低层模块,二者都应该依赖于其抽象;
抽象不应该依赖于细节,细节应该依赖于抽象。

高层模块包含一个应用程序中重要的策略选择和业务模块,如果高层模块依赖于低层模块,那么低层模块的改动就会直接影响到高层模块,从而迫使高层模块也需要改动。

依赖于抽象意味着:

  • 针对接口编程,不要针对实现编程。
  • 任何变量都不应该持有一个指向具体类的指针或者引用;
  • 任何类都不应该从具体类派生;
  • 任何方法都不应该覆写它的任何基类中的已经实现的方法。

优点:

  • 可以减少类间的耦合性,提高系统稳定性,提高代码可读性和可维护性,可降低修改程序所造成的风险。

举例

单一责任原则

一个类应该只有一个引起变化的原因。即一个类只做它该做的事情(事情是一个抽象的概念,高内聚)

  • 当我们能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。就应该考虑类的职责分离
  • 当我们允许一个类不但要完成自己的事情(管理某种聚合),还同时要担负更多的责任(如遍历)时,就给了这个类两个变化的原因
    • 当集合改变时,这个类必须改变,当遍历的方式改变的话,这个类也必须跟着改变。当有两个变化的原因,会使得该类的变化几率上升。
  • 换句话说就是让一个类只负责一件事,当这个类需要做过多事情的时候,就需要分解这个类。
    • 如果一个类承担的职责过多,就等于把这些职责耦合在了一起,一个职责的变化可能会削弱这个类完成其它职责的能力。
  • 一个类、接口、方法只负责一项职责。

举例

解决

将一个责任只指派给一个类。实现高内聚

接口分离原则

针对接口编程,而不是针对实现编程

不应该强迫客户依赖于它们不用的方法。

  • 该设计原则关注于类的扩展性,针对接口编程时,在运行时就可以使用该接口引用不同的子类,运行时动态改变。
  • 面对一个可能存在变化的类,将其变化抽出来制作一个接口,并针对接口进行不同的实现,从而使得类在”运行时”动态地”改变”
  • 这个抽离的接口可以被复用,因为他们的行为已经和原来的类没有了关系
  • 客户无需知道他们使用的对象的特定类型,只需要对象有客户期望的接口
  • 客户无需知道他们使用的对象是用什么类实现的,只需知道定义接口的抽象类
  • 通过如此设计,我们可以新增行为,不会影响到既有的行为类,也不会影响“使用”到该行为的类
  • 关键在于,现在这样的行为是委托给别人处理,而不是使用定义在父类或者子类内的方法

举例

当不得不在系统某个地方实例化具体的类(指定一个特定的实现)

创建型模式可以很好的解决该问题

迪米特法则

迪米特法则又叫作最少知识原则(Least Knowledge Principle,简写 LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。

一个对象应该对其他的对象保持最少的了解。

  • 强调只和朋友交流,不和陌生人说话。
    • 出现在成员变量、方法的输入、输出参数中的类称为成员朋友类,而出现在方法体内部的类不属于朋友类。
  • 尽量降低类与类间的耦合。
  • 降低类间的耦合。

举例

实现方法

就任何对象而言,在该对象的方法内,只应该调用属于以下范围的方法

  • 该对象本身
  • 被当做方法的参数而传递进来的对象
  • 此方法所创建或实例化的任何对象
  • 对象的任何组件
1
2
3
4
5
6
7
8
9
10
11
12
//调用从另一个调用中返回的对象的方法
//不采用该原则,这样相当于向另一个对象的子部分发出请求
//因此需要认识该对象的组件,不符合将朋友圈维持在最小状态
public float getTemp(){
Thermometer thermometer = station.getThermometer();//从气象站取得温度计
return thermometer.getThermometer();//从温度计获取温度
}
//采用该原则
//在station当中加入方法,直接获得温度
public float getTemp(){
return station.getThermometer();
}

缺点

虽然减少了对象间的依赖,减少了软件的维护成本。但是导致更多的“包装”类被制造出来,以处理和其他组件的沟通,可能导致复杂度、开发时间增加,降低运行时性能

里氏替换原则

任何时候都可以用子类型替换掉父类型。子类一定是增加父类的能力而不是减少父类的能力,因为子类的能力更多,把能力多的对象当成能力少的对象当然没有任何问题。

举例

合成复用原则

多用组合,少用继承

尽量使用对象组合,而不是通过继承来达到复用的目的。

举例

封装变化

找出应用中可能需要变化之处,把他们独立出来,不要和那些不需要变化的代码混合在一起

  • 把会变化的部分取出来并”封装”起来,好让其他部分不会受到影响
  • 即,如果每次新的需求到来,都会使得某方面的代码发生变化, 即确定,该部分的代码需要被抽取出来

举例

例如需要为Duck父类增加一个新的需求,fly方法。

错误的做法:

  • 在父类增加方法。这个时候,由于Duck的子类有一些事不需要fly的,例如橡皮鸭。此时就又需要去子类当中重写覆盖。
  • 新增一个fly的接口,但是此时由于接口不包含实现代码,因此需要在所有子类当中复现,更显繁琐

正确的做法:

  • 将动作分离出来成为一个behave的类,然后与Duck做一个组合

模式

  • 策略模式

为了使交互对象之间松耦合而努力

依赖抽象,不依赖具体类

举例

别找我,我会找你

由超类主控一切,当他们需要的时候,自然回去调用子类。

给出一种防止“依赖腐败”的方法。当高层组件依赖底低层组件,低层组件又依赖高层组件,而高层组件又依赖边侧组件,边侧组件又依赖低层组件时,即依赖腐败。没有人可以轻易搞懂系统如何设计的

该原则下,允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎么样使用这些低层组件。即低层组件不可用直接调用高层组件

举例

实现

在模板方法模式当中,模板类拥有着算法。只有在需要子类实现某个方法时,才调用子类。

客户代码只依赖于模板方法,而不依赖于具体的类,减少了整个系统的依赖。

如果子类没有先被调用,绝对不会直接调用抽象类。(在客户代码中,引用的是抽象类型,因此依赖就大大减少。、)

共同封闭原则

一起修改的类,应该组合在一起(同一个包里)。如果必须修改应用程序里的代码,我们希望所有的修改都发生在一个包里(修改关闭),而不是遍布在很多包里。

举例

稳定抽象原则

最稳定的包应该是最抽象的包,不稳定的包应该是具体的包,即包的抽象程度跟它的稳定性成正比。

举例

稳定依赖原则

包之间的依赖关系都应该是稳定方向依赖的,包要依赖的包要比自己更具有稳定性。

举例

某些有趣的事情

  • 和大佬们交流,使用观察者模式、策略模式等词,表述地更为方便一些
  • 但是书里面有一个趣谈:写一个helloWorld也和模式挂钩,hhhhh代表着模式病

参考

  1. [head first 设计模式]