设计模式

Posted by AaronYang on February 11, 2020

设计模式

六大原则

开闭原则

当应用的需求改变时,在不修改软件实体的源代码或者二进制代码的前提下,可以扩展模块的功能,使其满足新的需求。

通过 “抽象约束、封装变化” 来实现开闭原则,即通过接口或者抽象类为软件实体定义一个相对稳定的抽象层,而将相同的可变因素封装在相同的具体实现类中。

一个软件实体如类,模块和函数应该对扩展开放(对提供方),对修改关闭(对使用方)。用抽象构建框架,用实现扩展细节。

里氏替换原则

子类可以扩展父类的功能,但不能改变父类原有的功能。

子类继承时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。

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

依赖倒置原则

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

相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建的架构比以细节为基础的架构要稳定的多。在Java中,抽象指的是接口或抽象类,细节就是具体的实现类。

使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

依赖倒置原则的目的是通过要面向接口的编程来降低类间的耦合性:

  • 每个类尽量提供接口或抽象类,或者两者都具备。
  • 变量的声明类型尽量是接口或者是抽象类。
  • 任何类都不应该从具体类派生。
  • 使用继承时尽量遵循里氏替换原则。

依赖倒置原则的主要作用如下。

  • 依赖倒置原则可以降低类间的耦合性。
  • 依赖倒置原则可以提高系统的稳定性。
  • 依赖倒置原则可以减少并行开发引起的风险。
  • 依赖倒置原则可以提高代码的可读性和可维护性。

依赖关系传递的三种方式

  • 接口传递
  • 构造方法传递
  • setter方法传递

单一职责原则

单一职责原则规定一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。

单一职责原则的核心就是控制类的粒度大小、将对象解耦、提高内聚性。

单一职责原则注意事项和细节:

  1. 降低类的复杂度,一个类只负责一项职责;
  2. 提高类的可读性,可维护性
  3. 降低变更引起的风险

接口隔离原则

客户端不应该被迫依赖它不使用的方法,一个类对另一个类的依赖应该建立在最小的接口上。

要为各个类建立它们需要的专用接口,而不是试图去建立一个很庞大的接口供所有依赖它的类去调用。

  • 接口尽量小,但是要有限度。一个接口只服务于一个子模块或业务逻辑。
  • 为依赖接口的类定制服务。只提供调用者需要的方法,屏蔽不需要的方法。
  • 了解环境,拒绝盲从。每个项目或产品都有选定的环境因素,环境不同,接口拆分的标准就不同深入了解业务逻辑。
  • 提高内聚,减少对外交互。使接口用最少的方法去完成最多的事情。

类A通过接口Interface1依赖类B,类C通过接口Interface1依赖类D,如果接口Interface1对于类A和类C来说不是最小接口,那么类B和类D必须去实现它们不需要的方法;

将接口Interface1 拆分为独立的几个接口, 类A和类C分别与他们需要的接口建立依赖关系。也就是采用接口隔离原则;

接口Interface1中出现的方法,根据实际情况拆分为三个接口

迪米特法则

如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。

创建型模式

单例模式

某个类只能生成一个实例,该类提供了一个全局访问点供外部获取该实例,其扩展是有限多例模式。

原型模式

将一个对象作为原型,通过对其进行复制而克隆出多个和原型类似的新实例。

工厂方法模式

定义一个用于创建产品的接口,由子类决定生产什么产品。

抽象工厂模式

提供一个创建产品族的接口,其每个子类可以生产一系列相关的产品。

建造者模式

将一个复杂对象分解成多个相对简单的部分,然后根据不同需要分别创建它们,最后构建成复杂对象。

结构型模式

代理模式

为某对象提供一种代理以控制对该对象的访问。即客户端通过代理间接地访问该对象,从而限制、增强或修改该对象的一些特性。

适配器模式

将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

桥接模式

将抽象与实现分离,使它们可以独立变化。它是用组合关系代替继承关系来实现的,从而降低了抽象和实现这两个可变维度的耦合度。

装饰模式

动态地给对象增加一些职责,即增加其额外的功能。

外观模式

为多个复杂的子系统提供一个一致的接口,使这些子系统更加容易被访问。

享元模式

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

组合模式

将对象组合成树状层次结构,使用户对单个对象和组合对象具有一致的访问性。

行为型模式

模板方法模式

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

主要优点:

  • 封装不变部分,扩展可变部分

主要缺点

  • 不同的实现需要定义不同的子类
  • 子类执行的结果会影响父类的结果,这导致一种反向控制结构

命令模式

命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。

在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时使用命令模式来进行设计。

主要优点:

  • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
  • 方便实现 Undo 和 Redo 操作。

主要缺点:

  • 产生大量的具体命令类。

访问者模式

访问者模式(Visitor),封装一些作用于某种数据结构的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作。主要将数据结构与数据操作分离,解决数据结构和操作耦合性问题。

主要优点:

  • 扩展性好:可以在不修改对象结构的情况下,为对象结构中的元素添加新的操作;
  • 符合单一原则:把相关的操作封装在一起,构成一个访问者。

主要缺点:

  • 增加新的元素类很困难,每新增一个元素类,需要为每个访问者添加操作该元素的方法;
  • 违反了依赖倒置原则。

迭代器模式

迭代器(Iterator)模式 , 提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示 。

主要优点:

  • 访问一个聚合对象的内容而无须暴露它的实现
  • 增加新的聚合类和迭代器都很方便,无须修改原有代码。

主要缺点:

  • 增加类的个数,这在一定程度上增加了系统的复杂性

观察者模式

观察者(Observer)模式:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式。

主要优点:

  • 降低目标和观察者之间的耦合关系,两者之间是抽象耦合
  • 目标和观察者之间建立了一套触发机制

主要缺点:

  • 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  • 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

中介者模式

中介者(Mediator)模式:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。

主要优点:

  • 降低了对象之间的耦合度,使得对象易于独立地被复用
  • 将对象之间一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。

备忘录模式

备忘录(Memento)模式:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将改对象恢复到原先保存的状态。

主要优点:

  • 提供一种可恢复状态的机制
  • 实现了内部状态的封装。

主要缺点:

  • 资源消耗大。要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存

解释器模式

解释器(Interpreter)模式:给分析对象一个语言,并定义改语言的文法表示,再设计一个解释器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。

主要优点:

  • 扩展性好
  • 容易实现

主要缺点:

  • 执行效率低
  • 引起类膨胀
  • 应用场景少

状态模式

状态(State)模式:对有状态的对象,把复杂的逻辑判断提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

主要优点:

  • 状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”;

主要缺点:

  • 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱

策略模式

策略(Strategy)模式:改模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。

主要优点:

  • 可以避免多重条件语句
  • 把算法族的公共代码移到父类里面,从而避免重复的代码

主要缺点:

  • 客户端必须理解所有策略算法的区别,选着适合的算法类;
  • 造成很多的策略类

职责链模式

责任链(Chain of Responsibility)模式:为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一对象的引用而链接成一条链;当有请求发生时,可将请求沿着这条链传递,直到对象处理完毕。

主要优点:

  • 降低对象之间的耦合度;
  • 增强系统的可扩展性;
  • 责任分担

主要缺点:

  • 不能保证每个请求一定被处理
  • 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定的影响;