設計模式總結

說明

  • 以下內容基于個人理解,若有錯誤歡迎指正~
  • 本文中的代碼是為了助于理解而寫的偽代碼,并非專門某個語言的代碼(可能風格上比較貼近于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
http://www.lxweimin.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();
}

可以看出在策略模式下,我們將類中行為的變化部分改為了和其他類之間的聚合/組合關系,從而將操作解耦,此時策略也易于切換,并且每當要添加一種新的情況、策略時,只需要添加對應新的策略類即可。但由于對不同情況都要創建不同的策略類,會容易造成類過多的問題。該模式適合當多個類的區別僅在于他們的行為方法上、或者策略經常變更的場景。

這里有個基于鴨子問題來講解該模式的可以參考:http://www.lxweimin.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);
}

可以看出該模式在解釋時往往采用遞歸調用的方式,因此可能會導致程序效率降低等問題

解釋器模式還可以參考:http://www.lxweimin.com/p/f75440b05313

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容