那些容易混淆的设计模式,了解一下~

Posted 涂程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了那些容易混淆的设计模式,了解一下~相关的知识,希望对你有一定的参考价值。

好文推荐
作者:RicardoMJiang

前言

了解过设计模式的同学都知道,设计模式家族成员非常庞大,具体可以分为3类共23种设计模式
对我们来说,设计模式在种类上实在是有些多了,而且很多设计模式非常类似,让人傻傻分不清
本文主要介绍一些容易混淆的设计模式,以加深对设计模式的理解

本文主要包括以下内容

  1. 六大设计原则的介绍
  2. 简单工厂、工厂方法与抽象工厂模式的区别
  3. 代理,装饰与适配器模式的区别
  4. 策略、状态与命令模式的区别

设计原则介绍

众所周知,5大设计原则一般有6个

  • 单一职责原则(Single Responsibility Principle, SRP)
  • 开闭原则(Open Close Principle, OCP)
  • 里氏替换原则(Liskov Substitution Principle, LSP)
  • 依赖倒置原则(Dependence Inversion Principle, DIP)
  • 接口隔离原则(Interface Segregation Principle, ISP)
  • 迪米特法则(Law of Demeter, LoD),又称最少知识原则(Principle of Least Knowledge)

其中,前5个原则称为SOLID原则

单一职责原则

单一职责原则很简单也很好理解:类的职责应该单一,一个方法只做一件事,这里就不多说了

开闭原则

开闭原则是说程序对扩展开放,对修改关闭:当你的程序需要扩展时,不应该修改原来的代码,而是可以添加一个类来扩展。
比如商品打折,可以更改原来的商品类的getPrice 方法,也可以增加一个子类,重写getPrice方法,通过高层模块,在打折时使用子类。

开闭原则的思想其实是将不变的地方封装起来,将可变的部分暴露出去,这样它们就不会混淆,还可以复用不变的部分,增加扩展性
开闭原则的优点就在于:原来的代码不用更改,而且还可以复用原来的代码

里氏替换原则

里氏替换原则用一句话描述就是:所有引用基类的地方,必须能够使用其子类直接替换,换句话说,所有引用基类的地方必须透明地使用其子类对象。
但是这看起来有点奇怪,因为在Java中,引用基类的地方,本来就可以用子类替换,为什么要提出个原则?
这是因为子类可以重写父类的方法,如果父类中有个加法方法,在子类中重写成减法后,如果使用子类替换父类,会导致意料之外的问题

里氏替换原则告诉我们,继承实际上让两个类耦合性增强了,在子类中尽量不要重写父类得方法,在适当的情况下,可以通过聚合,组合,依赖来解决问题
通用的做法是:如果子类需要重写父类的方法,则可以将原有的继承关系去掉,原来的父类A和子类B都继承一个更通俗的基类,如果B需要使用到A的方法,可以使用组合的方式实现
具体示例可参见:设计模式之里氏替换原则

依赖倒置原则

所谓依赖倒置就是:高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口
比如一个Person类需要实现收取信息,需要依赖于Message类,这里的Person就是高层次模块,Message就是低层次模块
依赖倒置其实是说,Person不应该依赖于Message的具体实现,而是应该依赖于一个抽象的接口,方便后续扩展

所以说所谓依赖倒置就是面向接口编程,但这个名字的确不是很好理解

接口隔离原则

接口隔离原则简单来说就是:类不应该依赖它不需要的接口,即一个类对另一个类的依赖应该建立在最小的接口上
这与单一职责原则比较像,单一职责原则针对职责,从业务逻辑划分,而接口隔离原则则要求接口的方法尽量少
感觉这跟最小知识原则又比较像,一个类不应该知道它不需要的东西

最小知识原则

最小知识原则也很容易理解:一个对象应该对其他对象有最少的了解。最小知识原则对类的低耦合提出了明确的要求

简单工厂、工厂方法与抽象工厂模式的区别

工厂模式是我们经常用的模式之一,但是由于它可以细分为3种,很容易让人混淆
我们首先列出他们的定义:

  1. 简单工厂模式:又称为静态方法工厂模式,是由一个工厂对象决定创建哪一个产品类的实例。
  2. 工厂方法模式:创建一个用户创建对象的接口,让子类决定实例化哪个类。工厂方法使一个类的初始化延迟到其子类。
  3. 抽象工厂模式:为创建一组相关或者是相互依赖的对象提供一个接口,而不需要指定它们的具体类。

三种模式的UML图如下


可以看出它们的主要区别就在于

  1. 简单工厂模式:一个工厂方法创建不同类型的对象
  2. 工厂方法模式:一个具体的工厂类负责创建一个具体对象类型
  3. 抽象工厂模式:一个具体的工厂类负责创建一系列相关的对象

代理,装饰与适配器模式的区别

在结构型设计模式中,结构相似且比较容易混淆的模式有代理、装饰、适配器模式。首先,我们还是列出它们的定义以及UML图。

  1. 代理模式:为其他对象提供一种代理以控制对这个对象的访问
  2. 装饰模式:动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式相比生成子类更加灵活
  3. 适配器模式:把一个类的接口变换成客户端所期待的另一个接口,从而使原本因接口不匹配而无法工作的两个类能够在一起工作

三种模式的UML图分别如下:


它们的主要区别在于:

  1. 代理模式的特点在于隔离,隔离调用类和被调用类的关系,通过一个代理类去调用,因此代理模式不需要传入原有的对象,内部会持有原有对象的实现
  2. 装饰器模式特点在于增强,他的特点是被装饰类和所有的装饰类必须实现同一个接口,而且必须持有被装饰的对象,可以无限装饰,被装饰对象通过构造函数传入
  3. 适配器的特点在于兼容, 适配器模式需要实现新的接口,代理,装饰器模式是与原对象实现同一个接口,而适配器类则是匹配新接口

总的来说就是如下三句话:

  1. 代理模式是将一个类(a)转换成具体的操作类(b).
  2. 装饰模式是在一个原有类(a)的基础之上增加了某些新的功能变成另一个类(b).
  3. 适配器模式是将一个类(a)通过某种方式转换成另一个类(b).

策略、状态与命令模式的区别

策略,状态与命令模式,这三种设计模式都是行为型设计模式,在结构上又都很像,容易让人混淆,我们也首先来看下它们的定义与UML

  1. 策略模式:定义一组算法,将每个算法都封装起来,并且使它们之间可以互换
  2. 状态模式:状态模式中的行为是由状态来决定的,不同的状态下有不同的行为。允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
  3. 命令模式:将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或记录请求日志,可以提供命令的撤销和恢复功能

UML图如下所示


我们可以看到,策略与状态模式,它们的类图居然是一样的.虽然它们类型接口一样,但是它们的本质不一样。
区分这三种模式不要关注在结构上,这三种模式最主要是在使用意图上有区别:

  1. 策略模式:策略模式关注的是算法替换的问题,用一个新的算法替换旧算法,或者提供多种算法由调用者选择,算法的自由替换是它实现的重点
  2. 状态模式:状态模式策略模式很相似,也是将类的"状态"封装了起来,在执行动作时进行相应的替换,从而实现,类在不同状态下的同一动作显示出不同结果。它与策略模式的区别在于,这种转换是"自动","无意识"的。策略模式会控制对象使用什么策略,而状态模式会自动改变状态。状态模式内部维护一个状态,会随着public api的调用进行相应的状态转移。外界不需要知道状态及其变化情况。
  3. 命令模式:命令模式则关注的是解耦问题,如何让请求者和执行者解耦是它首先需要解决的,解耦的要求就是把请求的内容封装为一个个命令,由接收者执行。由于封装成了命令,就同时可以对命令进行多种处理,例如通过统一的execute接口执行命令,或者将命令存储起来,后续做撤销或者恢复功能

关于策略模式与状态模式对比的具体示例,可参见:策略模式 VS 状态模式

总结

本文主要介绍了设计模式六大原则,以及几种类似的容易让人混淆的设计模式。
希望可以通过分析比较这几种容易混淆的设计模式,更加深入地了解它们的异同与适用场景,加深对设计模式的了解

以上是关于那些容易混淆的设计模式,了解一下~的主要内容,如果未能解决你的问题,请参考以下文章

简述一下你了解的设计模式

ProGuard 代码混淆

append() 在这个代码片段中是如何工作的?与特定变量混淆[重复]

诚征译者 | 尝试尽自己所能,来解释我所了解的某些片段

ViewData和ViewBag初学者容易混淆的地方

CSS尝试性使用一下css容易混淆的属性选择器