美文网首页
设计模式总结

设计模式总结

作者: dawsonenjoy | 来源:发表于2020-03-07 00:13 被阅读0次

说明

  • 以下内容基于个人理解,若有错误欢迎指正~
  • 本文中的代码是为了助于理解而写的伪代码,并非专门某个语言的代码(可能风格上比较贴近于Java(其实稍稍改改就能用Java跑了)

设计原则

设计模式的关键是能够应对变化,以及更好地复用代码,其中有几个设计原则,这些原则是设计模式的评判标准,因此学习设计原则是基础,通过这些基础能够帮助我们更好的理解设计模式并根据这些原则设计自己的设计模式,下面介绍一下这些原则:

单一职责原则(SRP)

一个类只负责一项职责(但有时候假如类里的方法比较少,拆解类反而成本高不好,那么这个时候可以在一个类里拆解多个方法,使得在方法级别符合单一职责原则)

接口隔离原则(ISP)

客户程序不应该依赖他不需要的接口(方法),即一个类对另一个类的依赖(使用到另一个类,比如用到另一个类的方法)应该建立在最小接口上,所以遇到对应情况时,应该将接口拆解成多个最小功能接口。
例如People1类要工作、睡觉,People2类要上课、睡觉,假如定义一个接口Action里面有工作、上课和睡觉三个方法,那么对于People1来说,上课是不必要的,同理People2不需要工作,所以应该拆成三个接口:ActionAll(睡觉)、ActionWoker(工作)和ActionStudent(上课),那么People1的方法就是People1.睡觉(ActionAll)People1.工作(ActionWorker)People2的方法就是People2.睡觉(ActionAll)People2.上课(ActionStudent)
注:
依赖:有用到就是依赖,例如继承、实现、使用其方法等都属于依赖,继承和实现不用说,这里对使用其方法举个例子:在A类中传入B类的实例化对象(new A().method(new B())),就属于A依赖于B

依赖倒转原则(DIP)

高层模块不应该依赖低层模块,二者都应该依赖其抽象(接口或抽象类),或者说每个类应该都有其对应的接口,而不是孤零零的一个类在那里;抽象不应该依赖细节(实现类),细节应该依赖抽象;因为抽象相比于细节要稳定的多,所以应该制定好抽象,而把实现留给细节,说白了就是面向接口编程
例如People类需要交通工具出门,假如有一个Car类,此时可能定义成:People.run(Car),但是如果现在多了Train类,那么由于People.run只能传入Car,导致无法扩展Train的业务,所以最好让Car继承于Vehicle接口,然后People.run定义成People.run(Vehicle),此时如果要添加Train的业务,只需要让Train继承于Vehicle就可以了

里式替换原则(LSP)

子类一定能替代其父类,因此在继承时子类尽量不要重写父类的方法,如果需要重写,建议再弄一个基类,让子类和父类都分别继承于这个基类

开放封闭原则(OCP)

一个模块/类/函数应该对扩展开放,对修改关闭(例如出售商品的场景中,需要扩展商品种类,那么只需要定义新的商品类,而无需改写出售类的代码);用抽象扩展框架,实现扩展细节;对于需要变化时,尽量通过扩展软件实体的行为实现变化,而不是修改父类已有的代码。该原则是最重要的一个原则,之所以使用设计模式,遵循其他原则的目的,最终就是为了实现开放封闭原则

迪米特法则(LKP)

又称最少知识原则,要求一个对象应该对其他对象尽可能少的了解

合成复用原则

优先使用对象组合,而不是类继承

设计原则总结
  • 找出变化的地方把他独立出来,把不变的地方合起来提高复用
  • 面向接口编程
  • 多用组合、聚合,少用继承
  • 尽量做到高内聚低耦合

UML类图

几大关系

主要包括:类/接口/依赖/关联/泛化/实现/聚合/组合等,类和接口没什么好说的,这里主要介绍后面几个

关联

表达比如一对多、多对多的关系

泛化/实现

泛化可以理解为继承,实现就是对应接口的实现

依赖/聚合/组合

假如有类A如下:

class A{
  ...
}

那么下面这段就是B依赖A(使用到了A类):

class B {
  xxx(A a) {
    ...
  }
}

下面这段就是B聚合A(通过set方法传递了A类,聚合是关联关系中的一种特例,该关系下整体与部分可以分开,例如下面没有A,B也能在一定程度上正常运行):

class B {
  a = null;
  setA() {
    this.a = new A();
  }
  xxx() {
    ...
  }
}

下面这段就是B组合A(也是关联关系中的一种特例,相比聚合,其耦合性更强,该关系下整体和部分不能分开,例如下面没有A,B就运行不了了):

class B {
  a = new A();
  xxx() {
    ...
  }
}
UML图绘制

参考:
https://design-patterns.readthedocs.io/zh_CN/latest/read_uml.html
https://www.jianshu.com/p/57620b762160

设计模式

经典的设计模式有23种,分为三大类:创建型、结构型和行为型

创建型

由于对象的创建会消耗掉不少系统资源,因此创建型的设计模式主要就是探讨如何更加高效的创建对象,其主要有六种模式:单例模式、原型模式、创建者模式、简单工厂模式、工厂方法模式、抽象工厂模式,下面来一一介绍这些模式

单例模式

无论实例化多少次,都只有一个实例,从而减少内存的消耗。适合频繁创建和销毁的对象,以及一些重量级的对象,或者是那些经常用到的对象。实现单例模式有以下几种写法:

  • 饿汉式:加载时就实例化一个私有的,并且提供一个方法返回对象,举例:
class A {
  instance = new B();
  getB() {
    return instance;
  }
}

// 使用单例
main() {
  a1 = new A();
  a2 = new A();
  b1 = a1.getB();
  b2 = a2.getB();
  print(b1.equals(b2));  // true
}

此时不管实例化多少次,都只有一个B的实例化对象,但这种方式在一开始就加载,如果后面一直都没有使用,就会造成浪费

  • 懒汉式:在使用时才实例化一个私有的,举例:
class A {
  instance = null;
  getB() {
    if (instance == null) {
      instance = new B();
    }
    return instance;
  }
}

相比于饿汉式,这种方式只有在使用时才加载,避免了不使用时的浪费,但可能有线程安全的问题(假如多线程,同一时间不止一个线程进入了if判断,就不能保证实例化对象唯一了),因此在getB里需要通过设置线程锁或者同步处理等方式(根据不同语言来选择)来保证线程安全,不过这样也增加了线程控制的成本,造成总体效率偏低,当然根据语言的不同,肯定有比较好的处理方式,这里主要讲述懒汉式的思想,因此对于其产生的问题及解决方式就不进行详细说明

原型模式

主要用于克隆对象的场景,例如有一辆车,我们需要基于这辆车克隆出几辆一模一样的车(只是外观属性功能等完全一样,但并不是原来的那辆车),就会用到这种模式。该模式由于涉及到对象拷贝,可能就有深拷贝和浅拷贝的问题,此时在不同语言里可能实现方式差异较大,例如在Java里一般通过重写clone方法,或者序列化实现深拷贝;而python里可以通过copy.deepcopy实现深拷贝;JS里可以通过原型之类的实现

创建者模式

将产品和产品的创建过程分开,使得相同的创建过程可以创建出不同的产品,在创建者模式中主要有四个角色:

  • Product:产品角色,生成产品对象
  • AbsBuilder:抽象创建者,定义生产所需步骤
  • Builder:具体创建者,定义生产过程中各个步骤的具体实现
  • Director:指挥者,负责生产过程
    例如要创建一辆车,虽然牌子不同制作方式不同,但流程基本一样,那么就可以设计如下:
class Car {
  ...
}

class CarBuilder {
  car = new Car();
  design();
  builder();
  getCar() {
    return car;
  }
}

class BaomaBuilder extends CarBuilder {
  ...
}

class BenchiBuilder extends CarBuilder {
  ...
}

class CarBuilderDirector {
  buildCar(CarBuilder carBuilder) {
    carBuilder.design();
    carBuilder.builder();
    return carBuilder.getCar();
  }
}

// 创建车
main() {
  carBuilderDirector = new CarBuilderDirector();
  baoma = carBuilderDirector.buildCar(new BaomaBuilder()).getCar();
  benchi = carBuilderDirector.buildCar(new BenchiBuilder()).getCar();
}

使用创建者模式的产品间应该有很多相似的共同点

简单工厂模式

常用的一种设计模式,在工厂模式中创建对象时不会暴露创建逻辑,通过使用一个共同的接口指向一个新创建的对象(将实例化的代码提取出来,放在一个工厂类里来管理),例如要建造交通工具代码如下:

class Vehicle {
  ...
}

class Car extends Vehicle {
  ...
}

class Train extends Vehicle {
  ...
}

class VehicleFactory {
  createVehicle (name) {
    if (name.equals("car")) {
      return new Car();
    } else if (name.equals("train")) {
      return new Train();
    } else {
      return null;
    }
  }
}

// 使用工厂
main() {
  vehicleFactory = new VehicleFactory();
  vehicleFactory.createVehicle("car");
  vehicleFactory.createVehicle("train");
  ...
}

可以看出此时要创建交通工具,只需要往createVehicle里传入对应的交通工具名即可,使用简单好理解,但违反了开放封闭原则:如果要扩展新的交通工具时,需要创建新的交通工具类,并对工厂类的createVehicle方法进行改写(添加新的if分支)。该模式适合在需要创建大量某类、某对象时使用

工厂方法模式

将对象的实例化推迟到子类,在简单工厂模式当中,只有一个统一的工厂,所有的产品都在一个工厂里实现,假如说现在要分别创建陆地的交通工具和空中的交通工具等,那么这个时候就应该在不同的工厂建造不同类型的产品,此时就可以设计一个统一的生产接口方法,然后分别在生产陆地、空中交通工具的工厂里实现对应产品的生产方式,举例:

interface CreateVehicle {
  createVehicle();
}

class LandFactory implements CreateVehicle {
  createVehicle(name) {
    if (name.equals("car")) {
      return new Car();
    } else if (name.equals("train")) {
      return new Train();
    } else {
      return null;
    }
  }
}

class SkyFactory implements CreateVehicle {
  createVehicle(name) {
    if (name.equals("plane")) {
      return new Plane();
    } else if (name.equals("helicopter")) {
      return new Helicopter();
    } else {
      return null;
    }
  }
}

// 使用工厂
main() {
  Factory landFactory = new LandFactory();
  landFactory.createVehicle("car");
  landFactory.createVehicle("train");
  Factory skyFactory = new SkyFactory();
  skyFactory.createVehicle("plane");
  ...
}

可以看到具体的交通工具创建方法被分到了不同的工厂中实现:创建汽车、火车时使用到陆地工厂,创建飞机和直升机使用到空中工厂,如果要添加新的交通工具创建,则只需要修改对应的工厂(或者添加新的工厂),相比于简单工厂模式,扩展性更强

抽象工厂模式

对简单工厂模式的升级版,简单工厂模式返回一个产品类,而抽象工厂模式返回是一个抽象工厂接口,通过定义抽象工厂接口,而多个工厂则根据自己的情况实现对应的生产功能。该模式在工厂的产品种类多时,更易于扩展:

interface Factory {
  createVehicle();
}

class LandFactory implements Factory {
  createVehicle(name) {
    if (name.equals("car")) {
      return new Car();
    } else if (name.equals("train")) {
      return new Train();
    } else {
      return null;
    }
  }
}

class SkyFactory implements Factory {
  createVehicle(name) {
    if (name.equals("plane")) {
      return new Plane();
    } else if (name.equals("helicopter")) {
      return new Helicopter();
    } else {
      return null;
    }
  }
}

class CreateFactory {
  CreateFactory(factory) {
    if (factory.equals("land")) {
      return new LandFactory();
    } else if (factory.equals("sky")) {
      return new SkyFactory();
    } else {
      return null;
    }
  }
}

// 使用工厂
main() {
  landFactory = new CreateFactory("land");
  landFactory.createVehicle("car");
  landFactory.createVehicle("train");
  skyFactory = new CreateFactory("sky");
  skyFactory.createVehicle("plane");
  ...
}

可以看到抽象工厂模式下,对于同一工厂下的产品扩展是很容易的,但对于工厂的扩展,则是十分困难的,因此如果产品种类较为固定,那么使用简单工厂模式即可,如果产品类型很多,而工厂较为固定,建议使用抽象工厂模式

结构型

在解决了对象创建的问题以后,接下来该类型模式主要致力于解决对象之间的结构、依赖关系,主要有以下几种模式:外观模式、适配器模式、代理模式、装饰模式、桥接模式、组合模式、享元模式,下面来一一介绍这些模式

外观模式

通过定义一致的接口,从而屏蔽内部细节,例如夏天玩电脑前需要进行步骤:打开主机、打开屏幕、打开空调,关闭也同理需要一个个关掉,这个时候假如我们一个个去调用这些类的方法,将会十分繁琐,并且显得很杂乱,因此我们可以设计一个统一的接口,在接口当中实现统一的打开、关闭等操作,举例:

class Host {
  on {
    print("打开主机...");
  }
  play {
    print("玩游戏...");
  }
  off {
    print("关闭主机...");
  }
}

class Screen {
  on {
    print("打开屏幕...");
  }
  off {
    print("关闭屏幕...");
  }
}

class AirCon {
  on {
    print("打开空调...");
  }
  off {
    print("关闭空调...");
  }
}

class PlayGameControl {
  host = new Host();
  screen = new Screen();
  aircon = new Aircon();
  on() {
    host.on();
    screen.on();
    aircon.on();
  }
  play() {
    host.play();
  }
  off() {
    host.off();
    screen.off();
    aircon.off();
  }
}

// 玩电脑
main() {
  playGameControl = new PlayGameControl();
  playGameControl.on();
  playGameControl.play();
  playGameControl.off();
}

此时可以看到之前的所有步骤都被封装到了PlayGameControl类中,使用时我们无需再去一个个调用主机、屏幕和空调里的方法,只需要调用PlayGameControl类下的对应接口方法即可

适配器模式

类似于生活中的充电头(将插座220v的电压转成5v电压,从而给手机充电),将某个类的接口通过适配器转成客户端能够使用的另一种接口,可以看出适配器模式主要解决的是兼容性问题,主要分为类适配器、对象适配器和接口适配器,举例:

  • 类适配器:
class Plug {
  output() {
    return 220;
  }
}

class Usb extends Plug {
  chargeVolt() {
    return output() / 44;
  }
}

class Phone {
  charge(Usb usb) {
    print("充电电压:" + usb.chargeVolt());
  }
}

// 手机充电
main() {
  usb = new Usb();
  phone = new Phone();
  phone.charge(usb);
}
  • 对象适配器:
class Plug {
  output() {
    return 220;
  }
}

class Usb {
  Usb(Plug plug) {
    this.plug = plug;
  }
  chargeVolt() {
    return plug.output() / 44;
  }
}

class Phone {
  charge(Usb usb) {
    print("充电电压:" + usb.chargeVolt());
  }
}

// 手机充电
main() {
  usb = new Usb(new Plug());
  phone = new Phone();
  phone.charge(usb);
}
代理模式

为对象提供一个替身,以控制对这个对象的访问。即通过代理对象访问目标对象,因此可以在目标对象功能实现的基础上扩展额外的功能。例如:你想买衣服,但是老板卖的很贵,于是你可以找一个朋友来帮你买这件衣服,顺带砍价,举例:

interface People {
  buy();
}

class Me implements People {
  buy() {
    print("买衣服...");
  }
}

class MyFriendProxy implements People {
  MyFriendProxy(People people) {
    this.people = people;
  }
  buy() {
    print("先砍价!");
    people.buy();
  }
}

// 朋友代理我买衣服
main() {
  myFriendProxy = new MyFriendProxy();
  myFriendProxy.buy();
}

由于代理对象和目标对象要实现一样的接口,假如接口发生改变,那么代理类和目标类都要进行维护

装饰器模式

该模式通过创建一个装饰类来包装原有的类(装饰类和原有类的父类/接口应该一样,从而保证对原本的类不产生影响),从而扩展更多的功能,在不修改原来代码的基础上能够扩展新的功能,遵循了开放封闭原则,并且能够解决类爆炸问题。例如有一部手机要上网,这时候我们可以给他装载如运营商的配置、高速的配置等,由于配置有很多种,而且不是所有的配置都一定会用上,那么如果去一个个实现类,就会因为排列组合导致类数量爆炸。因此可以通过创建对应的装饰类,只需要把对象传入装饰类包装一下,即可装载上新的功能,代码如下:

class Phone {
  surf();
}

class MyPhone extends Phone {
  surf() {
    print("手机上网...");
  }
}

class SurfDecorator extends Phone {
  SurfDecorator(Phone phone) {
    this.phone = phone;
  }
  surf();
}

class YidongDecorator extends SurfDecorator {
  YidongDecorator(Phone phone) {
    super(phone);
  }
  surf() {
    phone.surf();
    print("配置移动上网...");
  }
}

class HighSpeedDecorator extends SurfDecorator {
  HighSpeedDecorator(Phone phone) {
    super(phone);
  }
  surf() {
    phone.surf();
    print("配置高速上网...");
  }
}

// 手机上网
main () {
  Phone myPhoneHighSpeed = new HighSpeedDecorator(new MyPhone());  // 配置高速上网手机
  Phone myPhoneYidongHighSpeed = new YidongDecorator(new HighSpeedDecorator(new MyPhone()));  // 配置移动高速上网手机
  myPhoneHighSpeed.surf();
  myPhoneYidongHighSpeed.surf();
  ...
}

可以看出假如需要有新的功能装载,只需要添加新的装饰类即可,并且对于原手机的装载,不影响原来的使用

桥接模式

将实现和抽象放在两个不同的类层次中,该模式基于类的最小设计原则。适合那些不希望因为继承等原因导致类的个数急剧增加的情况(避免类爆炸)。例如手机上网,使用的运营商有移动、联通等,并且速度可能基于高速和低速等,那么可能设计类如下:

class Company {
  surf();
}

class Yidong extends Company {
  surf() {
    print("移动上网");
  }
}

class Liantong extends Company {
  surf() {
    print("联通上网");
  }
}

class YidongHighSpeed extends Yidong {
  surf() {
    super();
    print("高速...")
  }
}

class YidongLowSpeed extends Yidong {
  surf() {
    super();
    print("低速...")
  }
}

class LiantongHighSpeed extends Liantong {
  ...
}

class LiantongLowSpeed extends Liantong {
  ...
}

// 手机上网
main() {
  yidongHighSpeed = new YidongHighSpeed();
  yidongHighSpeed.surf();
  ...
}

此时如果每加一种情况,例如运营商加个电信,那么高速和低速的类里都得加一遍,加个中速,那么移动和联通里也都得加子类(有种排列组合的感觉),从而导致类剧增的情况。因此这时候改为基于桥接模式,将抽象和实现部分分离,可以避免这种类爆炸的情况,代码如下:

class Speed {
  surf();
}

class HighSpeed extends Speed {
  surf() {
    print("高速...")
  }
}

class LowSpeed extends Speed {
  surf() {
    print("低速...")
  }
}

class Company {
  surf();
}

class Yidong extends Company {
  Yidong(Speed speed) {
    this.speed = speed;
  }
  surf() {
    print("移动上网");
    speed.surf();
  }
}

class Liantong extends Company {
  Liantong(Speed speed) {
    this.speed = speed;
  }
  surf() {
    print("联通上网");
    speed.surf();
  }
}

// 手机上网
main () {
  yidongHighSpeed = new Yidong(new HighSpeed());
  yidongHighSpeed.surf();
  ...
}

可以看出基于桥接模式修改后的代码,假如要增加一个运营商或者中速,都只需要添加一个类即可,从而避免了大量创建类的问题

组合模式

又称为整体部分模式,该模式下创建了对象组的树形结构,适合那种需要遍历组织结构或者有树形结构层次,并且这些组织结构的功能和属性也十分相似的场景,并且树形当中的内容都有相似的结构,例如:交通工具分汽车、火车等,而汽车、火车下又分不同的牌子、型号等,并且他们都有相似的接口方法和属性,此时用组合模式举例:

class Component {
  String name;
  Component(name) {
    this.name = name;
  }
  getName() {
    return name;
  }
  add();
  show();
}

class Vehicle extends Component {
  ArrayList<Component> list = new ArrayList<Component>();
  Vehicle(name) {
    super(name);
  }
  add(Component component) {
    list.add(component);
  }
  show() {
    print(this.getName());
    for (Component component: list) {
      component.show();
    }
  }
}

class Car extends Component {
  Car(name) {
    super(name);
  }
  show() {
    print(this.getName());
  }
}

// 建立层次关系
main() {
  car = new Vehicle("car");
  car.add(new Car("baoma"));
  car.add(new Car("benchi"));
  car.show();
}

可以看出该模式下所有的车的类型都在Car下,所有的交通工具类型都在Vehicle下,并且都有统一的接口方法show和属性name,十分便于管理。在Java的Hashmap下由于键值对可能会一层套一层,因此就使用了该模式

享元模式

当系统中有大量相似对象时,会造成内存浪费的问题,因此我们就可以建立一个集合来管理各种对象,此时对于已经存在的对象就直接返回拿去用,不存在的才进行创建并返回,从而保证各类对象都只有一个,这就是享元模式,使用享元模式最常见的场景就是池技术。在享元模式中将信息分为内部状态(存储在对象内部基本不会变的内容,可共享的部分)和外部状态(随环境而改变的,不可共享的部分),这里我们就来基于享元模式设计一个伪数据库连接池:

class SqlConnection {
  connection(name);
}

class UserSqlConnection extends SqlConnection {
  UserSqlConnection(table) {
    this.table = table;
  }
  connection(name) {
    print("用户:" + name + " 连接表:" + table);
  }
}

class SqlPool {
  HashMap<String, UserSqlConnection> map = new HashMap<>();
  getConnection(table) {
    if (table not in map) {
      map.put(table, new UserSqlConnection(table));
    }
    return map.get(table);
  }
}

// 使用数据库连接池
main() {
  sqlPool = new SqlPool();
  conn1 = sqlPool.getConnection("table1");
  conn1.connection("people1");
  conn2 = sqlPool.getConnection("table2");
  conn2.connection("people2");
  conn3 = sqlPool.getConnection("table1");
  conn3.connection("people3");
}

可以看出此时conn1conn3使用的是同一个对象,实现了复用的效果,并且因为table名是可共享的,因此在这里是内部状态,而name是不可共享的,在这里就是外部状态

行为型

在对象创建和结构问题都解决后,就只剩下行为问题,即为不同实体间提供更灵活、简易的通信方法,为了清晰对象的行为,并提高协作的效率,而提出了以下的行为模式:模板方法模式、观察者模式、状态模式、策略模式、职责链模式、命令模式、访问者模式、中介者模式、备忘录模式、迭代器模式、解释器模式,下面来一一介绍这些模式

模板方法模式

对于一些流程基本相同,只是流程中的具体操作不一样的场景,就可以使用模板方法模式:定义好流程中需要执行的方法,并定义好一个执行流程的模板框架,而具体的执行操作由子类去实现,例如期末考有:复习、考试和查成绩的步骤,那么就可以设计如下:

class FinalExam {
  review();
  exam() {
    print("考试中...");
  }
  search() {
    print("查成绩");
  }
  do() {
    review();
    exam();
    search();
  }
}

class Math extends FinalExam {
  review() {
    print("复习高数...");
  }
}

class English extends FinalExam {
  review() {
    print("复习英语...");
  }
}

// 期末开始
main() {
  math = new Math();
  math.do();
  english = new English();
  english.do();
}
观察者模式

该模式是对象之间多对一的一种设计方案,多个Observer定制Subject的消息,当Subject变化时将通知所有的Observer,因此Subject会对Observer提供注册、移除和推送消息的功能,而Observer里则需要有一个update信息的功能,常见于发布订阅的场景,这里我们模拟一个简单的公众号推送场景:

interface Subject {
  regist();
  remove();
  notify();
}

class WechatSubject implements Subject {
  List<Observer> observers = new List<Observer>();
  notice = "无通知";
  regist(Observer observer) {
    observers.add(observer);
  }
  remove(Observer observer) {
    observers.remove(observer);
  }
  notify() {
    for (observer: observers) {
      observer.update(notice);
    }
  }
  setNotice(notice) {
    this.notice = notice;
    notify();
  }
}

Interface Observer {
  update(notice);
}

class ObserverA implements Observer {
  update(notice) {
    print("A收到通知:" + notice);
  }
}

class ObserverB implements Observer {
  update(notice) {
    print("B收到通知:" + notice);
  }
}

// 发布订阅
main() {
  wechatSubject = new WechatSubject();
  observerA = new ObserverA();
  observerB = new ObserverB();
  wechatSubject.regist(observerA);
  wechatSubject.regist(observerB);
  wechatSubject.setNotice("新通知!");
}

可以看出此时如果要添加新的订阅者只需要创建对应观察者类,然后通过Subject提供的regist方法注册后即可接收到订阅信息

状态模式

该模式主要适用于对象存在多种状态,且在不同状态时可能有不同行为的场景(其中状态和行为是一一对应的,状态之间可以互相跳转)。例如对于一个订单假如有未支付、已支付和已出货三个流程,并且有支付和出货两个操作,那么显然在不同状态下,这两个操作的对应结果是可能不同的,比如在未支付的情况下,出货操作是被禁止的,但支付以后是允许出货操作的,而且支付成功将会从未支付转成已支付状态,支付失败则回到未支付状态。像这种流程逻辑复杂的情况,虽然也能用if/else来处理,但可能会造成代码混乱可读性差,而且很容易漏掉某些情况,从而导致出错,所以这时候就建议使用状态模式来处理。这里我们就模拟一下订单支付的流程状态:

class State {
  State(Context context) {
    this.context = context;
  }
  paid();
  checkPaid();
  output();
  display();
}

class UnpaidState extends State {
  paid() {
    print("请付款!");
    context.setState(context.getPaidState());
  }
  checkPaid() {
    print("未付款,请先付款!")
  }
  output() {
    print("未付款,无法出货!");
  }
  display() {
    print("当前状态:未支付");
  }
}

class PaidState extends State {
  paid() {
    print("已付款!");
  }
  checkPaid() {
    print("付款成功!");
    context.setState(context.getOutputState());
  }
  output() {
    print("等待出货中!");
  }
  display() {
    print("当前状态:已支付");
  }
}

class OutputState extends State {
  paid() {
    print("已付款!");
  }
  checkPaid() {
    print("付款成功!");
  }
  output() {
    print("已出货!");
  }
  display() {
    print("当前状态:已出货");
  }
}

class Context {
  unpaidState = new UnpaidState(this);
  paidState = new PaidState(this);
  outputState = new OutputState(this);
  state = unpaidState;
  setState(State state) {
    this.state = state;
    this.state.display();
  }
  getState() {
    return state;
  }
  paid() {
    state.paid();
  }
  checkPaid() {
    state.checkPaid();
  }
  output() {
    state.output();
  }
  getUnpaidState() {
    return unpaidState;
  }
  getPaidState() {
    return paidState;
  }
  getOutputState() {
    return outputState;
  }
}

// 购买物品
main() {
  context = new Context();
  context.paid();
  context.checkPaid();
  context.output();
}

可以看出在该模式下我们对每种状态该如何处理可以有很清晰的思路,但如果状态过多的话容易产生很多的类,可能会增大维护难度

策略模式

该模式将类的变化部分从中分离出来,编写成一个独立类,并统一通过一个上下文来管理,从而方便变化的部分可以在多种情况中互相切换(关键是分离出变化和不变的部分,对于行为类的关系多用组合和聚合来代替继承)。例如对于一件事,在A/B/C三种情况下我们分别有计划A/B/C来应对,我们先用普通的方式来实现看看:

class DoPlan {
  do(String situation) {
    if (situation is "A") {
      new PlanA().do();
    } else if (situation is "B") {
      new PlanB().do();
    } else if (situation is "C") {
      new PlanC().do();
    }
  }
}

class PlanA {
  do() {
    print("执行计划A");
  }
}

class PlanB {
  do() {
    print("执行计划B");
  }
}

class PlanC {
  do() {
    print("执行计划C");
  }
}

// 执行计划
main() {
  situation = "A";
  doPlan = new DoPlan();
  doPlan.do(situation);
}

可以看出普通的方式里由于使用了许多if/else语句容易导致代码混乱,并且每增加一个新的情况和策略,都得在原本的方法中进行修改,违反了OCP原则,接下来我们再来试试策略模式:

class Strategy {
  do();
}

class StrategyA extends Strategy {
  do() {
    print("执行计划A");
  }
}

class StrategyB extends Strategy {
  do() {
    print("执行计划B");
  }
}

class StrategyC extends Strategy {
  do() {
    print("执行计划C");
  }
}

class Context {
  Context(Strategy strategy) {
    this.strategy = strategy;
  }
  setStrategy(Strategy strategy) {
    this.strategy = strategy;
  }
  do() {
    strategy.do();
  }
}

// 执行计划
main() {
  context = new Context(new StrategyA());
  context.do();
  context.setStrategy(new StrategyB());
  context.do();
}

可以看出在策略模式下,我们将类中行为的变化部分改为了和其他类之间的聚合/组合关系,从而将操作解耦,此时策略也易于切换,并且每当要添加一种新的情况、策略时,只需要添加对应新的策略类即可。但由于对不同情况都要创建不同的策略类,会容易造成类过多的问题。该模式适合当多个类的区别仅在于他们的行为方法上、或者策略经常变更的场景。

这里有个基于鸭子问题来讲解该模式的可以参考:https://www.jianshu.com/p/422acad380dd

职责链模式

该模式会对行为请求创建一个接收者对象的链,即每个接收者基本都包含对下一个接收者的引用,如果当前接收者能处理该请求就直接处理,如果无法处理该请求,就会将该请求传递给下一个接收者,以此类推。例如要报销一批东西,对应不同职责的人能批下来的报销金额不同,那么就会形成一个职责链,我们来模拟一下这个场景:

class Approver {
  setApprover(Approver approver) {
    this.approver = approver;
  }
  handleRequest(int price);
}

class LeaderLowApprover extends Approver {
  handleRequest(int price) {
    if (0 < price <= 1000) {
      print("小额报销成功!")
    } else {
      approver.handleRequest(price);
    }
  }
}

class LeaderMiddleApprover extends Approver {
  handleRequest(int price) {
    if (1000 < price <= 10000) {
      print("中额报销成功!")
    } else {
      approver.handleRequest(price);
    }
  }
}

class LeaderHighApprover extends Approver {
  handleRequest(int price) {
    if (10000 < price <= 100000) {
      print("大额报销成功!")
    } else {
      print("太多了,不给报销!")
    }
  }
}

// 报销流程
main() {
  leaderLowApprover = new LeaderLowApprover();
  leaderMiddleApprover = new LeaderMiddleApprover();
  leaderHighApprover = new LeaderHighApprover();
  leaderLowApprover.setApprover(leaderMiddleApprover);
  leaderMiddleApprover.setApprover(leaderHighApprover);
  leaderLowApprover.handleRequest(99999);
}

可以看出在该模式下通过设置对应的责任链(有时根据场景,可能需要将责任链设置为环形,例如斗兽棋的关系:狮子<大象<老鼠<...),在设置好起始的处理点,即可很好的完成任务分配。但要注意的是假如职责链过长,那么会因为过多地请求传递而造成性能上的下降,并且也会造成调试困难等问题。该模式适合分级请求、审批流程等场景。

命令模式

该模式将请求对象和执行对象解耦,发起请求的对象是调用者,调用者只要调用命令对象的执行方法即可让接收者工作,命令对象一般就包含执行和撤销两个操作,拿夏天玩游戏举例,需要打开主机、屏幕和空调,那么就可以对打开这些设备分别命令类,到时候只需要调用命令类的操作即可对对应的设备进行相应操作(听起来可能和外观模式有点类似,但外观模式是对这些设备的操作定义一个统一的接口进行管理,而命令模式则是基于一个个命令去创建类来管理,前者是一个类包括所有命令,只是去调用这些类的命令接口,而后者是对一个个命令创建类,并创建对应类的接收者,通过命令类对接收者进行操作),举例:

class HostReceiver {
  on() {
    print("主机开机...");
  }
  off() {
    print("主机关机...");
  }
}

class Command {
  do();
  undo();
}

class HostOnCommand extends Command {
  HostOnCommand(HostReceiver host) {
    this.host = host;
  }
  do() {
    host.on();
  }
  undo() {
    host.off();
  }
}

class HostOffCommand extends Command {
  HostOffCommand(HostReceiver host) {
    this.host = host;
  }
  do() {
    host.off();
  }
  undo() {
    host.on();
  }
}

class RemoteControl {
  HashMap<String, Command> map = new HashMap<>();
  add(name, Command) {
    map.put(name, Command);
  }
  pushOn(name) {
    map.get(name).do();
  }
  pushOff(name) {
    map.get(name).undo();
  }
}

// 将命令集成遥控上进行操作
main() {
  remoteControl = new RemoteControl();
  host = new HostReceiver();
  hostOnCommand = new HostOnCommand(host);
  hostOffCommand = new HostOffCommand(host);
  remoteControl.add("hostOn", hostOnCommand);
  remoteControl.add("hostOff", hostOffCommand);
  remoteControl.pushOn("hostOn");
  remoteControl.pushOff("hostOn");
}
访问者模式

该模式将数据结构和操作进行分离,在被访问的类里加入一个提供对外访问者的接口,适合数据结构稳定,但功能需求经常发生变化的场景,例如现在固定只有男性和女性访客,需要对产品进行好评和差评操作,那么就可以设计如下:

class Visitor {
  visit(Man man);
  visit(Woman woman);
}

class HighOpinionVisitor extends Visitor {
  visit(Man man) {
    print("男性给了好评...");
  }
  visit(Woman woman) {
    print("女性给了好评...");
  }
}

class LowOpinionVisitor extends Visitor {
  visit(Man man) {
    print("男性给了差评...");
  }
  visit(Woman woman) {
    print("女性给了差评...");
  }
}

class People {
  accept(Visitor visitor);
}

class Man extends People {
  accept(Visitor visitor) {
    visitor.visit(this);
  }
}

class Woman extends People {
  accept(Visitor visitor) {
    visitor.visit(this);
  }
}

// 男性和女性进行评价
main() {
  man = new Man();
  woman = new Woman();
  highOpinion = new HighOpinionVisitor();
  lowOpinion = new LowOpinionVisitor();
  man.accept(highOpinion);
  woman.accept(lowOpinion);
}

此时如果需要添加一个中评,只需要添加一个中评类即可,但是如果多了男性和女性以外的群体时,那么几个操作类里就都需要修改(添加对应的构造方法),因此适合访问者稳定不变,而操作经常改变的情况

这篇感觉讲的挺不错可以参考:https://blog.csdn.net/eyabc/article/details/80737226

中介者模式

又称为调停者模式,通过一个中介对象来封装一系列的对象交互,从而使得各个对象间可以不用显示的相互引用,例如MVC模式中Controller就充当了中介者的角色,所有的Model操作基于Controller实现,而Model和View之间是没有互相引用操作的,前台View只需要负责接收Controller返回的结果即可。再比如买房的时候卖家和买家首先要找同一个中介(中介也需要拿个小本本记下来自己都要负责沟通的人),然后这期间的传话就通过中介来进行沟通,这里就来模拟一个基于中介买房的例子:

class Mediator {
  HashMap<String, People> map = new HashMap<String, People>();
  regist(People people);
  getMessage(String name, int price) ();
}

class HouseMediator extends Mediator {
  regist(People people) {
    map.put(people.name, people);
  }
  getMessage(String name, int price) {
    people = map.get(name);
    if (people is buyer) {
      print("买家发话:")
    } else if(people is seller) {
      print("卖家发话:")
    }
    people.do(price);
  }
}

class People {
  People(String name, Mediator mediator) {
    this.name = name;
    this.mediator = mediator;
    this.mediator.regist(this);
  }
  sendMessage(int price);
}

class Buyer extends People {
  sendMessage(int price) {
    mediator.getMessage(name, price);
  }
  do(int price) {
    print("最多出价:" + price);
  }
}

class Seller extends People {
  sendMessage(int price) {
    mediator.getMessage(name, price);
  }
  do(int price) {
    print("最少也要:" + price);
  }
}

// 找中介交易房子
main() {
  mediator = new HouseMediator();
  buyer = new Buyer("Jack", mediator);
  seller = new Seller("Rose", mediator);
  buyer.sendMessage(1000000);
  seller.sendMessage(2000000);
}

可以看出在中介者模式下,所有角色都以中介为中心,将一个可能的网状结构转变成了星型结构,从而降低了耦合。要注意的是一旦中介者职责过多,当中介者出了问题,将可能对整体造成一定的影响,所以这种情况要尽量避免

备忘录模式

在不破坏封装性的前提下,获取一个对象的内部状态,并在该对象之外保存这些状态,从而当需要时,该对象可以回退到先前的某个状态。该模式应用的场景很多,如虚拟机快照、数据库事务、撤销操作,以及游戏存档等。这里我们来模拟一个游戏存档的场景:

class Memento {
  Memento(String state) {
    this.state = state;
  }
  getState() {
    return state;
  }
}

class Player {
  state = "lv.0";
  setState(String state) {
    this.state = state;
  }
  saveState() {
    return new Memento(state)
  }
  showState() {
    print("当前状态:" + state);
  }
  backToState(Memento memento) {
    state = memento.getState();
  }
}

class Caretaker {
  HashMap<String, Memento> map = new HashMap<String, Memento>();
  add(String msg, Memento memento) {
    map.put(msg, memento);
  }
  getState(String msg) {
    return map.get(msg);
  }
}

// 游戏存档
main() {
  caretaker = new Caretaker();
  player = new Player();
  caretaker.add("初始状态", player.saveState());
  player = setState("lv.100");
  caretaker.add("满级", player.saveState());
  player.showState();
  player.backToState(caretaker.getState("初始状态"));
  player.showState();
}
迭代器模式

提供遍历集合的统一接口,用一致的方式遍历对象中的集合元素,而无需暴露对象的内部结构。其实就是平常用的迭代器,下面我们来模拟一个迭代器遍历场景:

interface Interator {
  hasNext();
  next();
}

class StudentsInterator implements Interator {
  index = 0;
  StudentsInterator(Students students) {
    this.students = students;
  }
  hasNext() {
    if (index < students.length) {
      return true;
    }
    return false;
  }
  next() {
    if (hasNext()) {
      return students[index++];
    }
    return null;
  }
}

class Students {
  students = ["mike", "john", "bob"];
  getInterator() {
    return new StudentsInterator(students);
  }
}

// 使用迭代器
main() {
  studentsInterator = new Students().getInterator();
  while(studentsInterator.hasNext()) {
    print(studentsInterator.next());
  }
}
解释器模式

当有一个语言需要解释,并且可以将语言的内容用抽象语法树来表达,那么就可以使用解释器模式。常用的场景有:编译器、运算表达式、正则表达式等。这里来模拟一下加法运算式的解析:

class Expression {
  interpret();
}

class NumExpression extends Expression {
  NumExpression(int value) {
    this.value = value;
  }
  interpret() {
    return value
  }
}

class AddExpression extends Expression {
  AddExpression(Expression left, Expression right) {
    this.left = left;
    this.right = right;
  }
  interpret() {
    return left.interpret() + right.interpret();
  }
}

class Calculator {
  Stack<Expression> stack = new Stack<Expression>();
  analysis(String expression) {
    for (i = 0; i < expression.length; i++) {
      if (elements[i] is number) {
        stack.push(new NumExpression(elements[i]));
      } else if (elements[i] is "+") {
        left = stack.pop();
        right = new NumExpression(elements[++i]);
        stack.push(new AddExpression(left, right));
      }
    }
  }
  compute() {
    return stack.pop().interpret();
  }
}

// 加法运算
main() {
  calculator = new Calculator();
  calculator.analysis("1+2+3");
  result = calculator.compute();
  print("计算结果:" + result);
}

可以看出该模式在解释时往往采用递归调用的方式,因此可能会导致程序效率降低等问题

解释器模式还可以参考:https://www.jianshu.com/p/f75440b05313

相关文章

网友评论

      本文标题:设计模式总结

      本文链接:https://www.haomeiwen.com/subject/tlgxmctx.html