《設計模式》這樣學就對了!

李運通,微醫前端技術部前端工程師。最怕你一生碌碌無爲,還安慰自己平凡可貴。

設計模式解決什麼痛點?

它是解決特定問題的一系列套路,是前輩們的代碼設計經驗的總結,具有一定的普遍性,可以反覆使用。其目的是爲了提高代碼的可複用性、可讀性、可維護性
設計模式的本質是面向對象設計原則的實際運用,是對類的封裝性、繼承性和多態性以及類的關聯關係和組合關係的充分理解。
不要重複造輪子

什麼是面向對象編程

10 大設計原則

1. 單一職責原則 SRP

實現類要職責單一:如果一段代碼塊(函數 類 模塊)負責多個功能,那麼當 A 功能需求發生改變的時候改動了代碼,就有可能導致 B 功能出現問題,所以一段代碼塊只應該負責一個職責。

2. 開放 - 封閉原則 OCP

要對擴展開放,對修改關閉:通過修改老代碼來實現新功能可能導致老模塊出現 BUG,所以我們應該通過開發新代碼塊來實現新功能

3. 里氏替換原則 LSP

不要破壞繼承體系:程序中的子類應該可以替換父類出現的任何地方並保持預期不變。所以子類儘量不要改變父類方法的預期行爲

4. 接口隔離原則 ISP

設計接口的時候要精簡單一:當類 A 只需要接口 B 中的部分方法時,因爲實現接口需要實現其所有的方法,於是就造成了類 A 多出了部分不需要的代碼。這時應該將 B 接口拆分,將類 A 需要和不需要的方法隔離開來

5. 依賴倒置原則 DIP

面向接口編程:抽象不應該依賴細節,細節應該依賴於抽象。核心是面向接口編程,我們應該依賴於抽象接口,而不是具體的接口實現類或具體的對象

注意:上面的 SOLID 又稱爲 5 大設計原則

6. 最少知識原則 (迪米特原則)LOD

降低耦合度:一個類或對象應該對其它對象保持最少的瞭解。只與直接的朋友 (耦合) 通信。

7. 組合 / 聚合複用原則 CRP

多用組合少用繼承:儘可能通過組合已有對象(借用他們的能力)來實現新的功能,而不是使用繼承來獲取這些能力

8. 不要重複你自己 DRY

功能語義重複應該合併,代碼執行重複應該刪減,代碼邏輯重複但語義不同應該保留

9. 儘量保持簡單 KISS

儘可能使用簡單可讀性高的代碼實現功能,而不用邏輯複雜、實現難度高、可讀性差的方式

10. 不要過度設計你暫時用不到的邏輯 YAGNI

不要過度優化、不要過度預留擴展點、不要設計同事看不懂的代碼。

23 種設計模式速記

創建型設計模式

封裝對象創建過程,將對象的創建和使用解耦

單例模式

應用場景

處理資源訪問衝突、用來創建全局唯一類

解決方案

工廠模式

應用場景

用來創建繼承同一父類、實現同一接口的子類對象,由給定的類型參數創建具體的對象。

解決方案

enum HelloType {
  A,
  B
}

interface Hello {
  sayHello()
}

class A implements Hello {
  sayHello() {
    console.log('A');
  }
}

class B implements Hello {
  sayHello() {
    console.log('B');
  }
}

class HelloFactory {
  static list = new Map<HelloType, Hello>([
    [HelloType.A, new A()],
    [HelloType.B, new B()]
  ])

  static getHello(type: HelloType) {
    return HelloFactory.list.get(type)
  }
}

// test
HelloFactory.getHello(HelloType.A).sayHello()
HelloFactory.getHello(HelloType.B).sayHello()

抽象工廠模式

應用場景

繼承同一父類、實現同一接口的子類對象,由給定的多個類型參數創建具體的對象。

解決方案

enum Type {
  A,
  B
}

enum Occupation {
  TEACHER,
  STUDENT
}

interface Hello {
  sayHello()
}

class TA implements Hello {
  sayHello() {
    console.log('Teacher A say hello')
  }
}

class TB implements Hello {
  sayHello() {
    console.log('Teacher B say hello')
  }
}

class SA implements Hello {
  sayHello() {
    console.log('Student A say hello')
  }
}

class SB implements Hello {
  sayHello() {
    console.log('Student B say hello')
  }
}

class AFactory {
  static list = new Map<Occupation, Hello>([
    [Occupation.TEACHER, new TA()],
    [Occupation.STUDENT, new SA()]
  ])

  static getHello(occupation: Occupation) {
    return AFactory.list.get(occupation)
  }
}

class BFactory {
  static list = new Map<Occupation, Hello>([
    [Occupation.TEACHER, new TB()],
    [Occupation.STUDENT, new SB()]
  ])

  static getHello(occupation: Occupation) {
    return BFactory.list.get(occupation)
  }
}

class HelloFactory {
  static list = new Map<Type, AFactory | BFactory>([
    [Type.A, AFactory],
    [Type.B, BFactory]
  ])

  static getType(type: Type) {
    return HelloFactory.list.get(type)
  }
}

// test
HelloFactory.getType(Type.A).getHello(Occupation.TEACHER).sayHello()
HelloFactory.getType(Type.A).getHello(Occupation.STUDENT).sayHello()
HelloFactory.getType(Type.B).getHello(Occupation.TEACHER).sayHello()
HelloFactory.getType(Type.B).getHello(Occupation.STUDENT).sayHello()

建造者模式

應用場景

解決方案

class Programmer {
  age: number
  username: string
  color: string
  area: string

  constructor(p) {
    this.age = p.age
    this.username = p.username
    this.color = p.color
    this.area = p.area
  }

  toString() {
    console.log(this)
  }
}

class Builder {
  age: number
  username: string
  color: string
  area: string

  build() {
    if (this.age && this.username && this.color && this.area) {
      return new Programmer(this)
    } else {
      throw new Error('缺少信息')
    }
  }

  setAge(age: number) {
    if (age > 18 && age < 36) {
      this.age = age
      return this
    } else {
      throw new Error('年齡不合適')
    }
  }

  setUsername(username: string) {
    if (username !== '小明') {
      this.username = username
      return this
    } else {
      throw new Error('小明不合適')
    }
  }

  setColor(color: string) {
    if (color !== 'yellow') {
      this.color = color
      return this
    } else {
      throw new Error('yellow不合適')
    }
  }

  setArea(area: string) {
    this.area = area
    return this
  }
}

// test
const p = new Builder()
  .setAge(20)
  .setUsername('小紅')
  .setColor('red')
  .setArea('hz')
  .build()
  .toString()

原型模式

應用場景

結構型設計模式

總結了一些類或對象組合在一起的經典結構,這些經典結構可以解決特定應用場景的問題,將類或對象的結構和使用解耦

橋接模式

應用場景

解決方案

enum MsgLevel {
  ERROR,
  WARN,
}

enum MsgType {
  EMAIL,
  PHONE
}

interface MsgContent {
  content()
}

class ErrorMsg implements MsgContent {
  content() {
    return 'ERROR'
  }
}

class WarnMsg implements MsgContent {
  content() {
    return 'WARN'
  }
}

interface MsgSender {
  send()
}

class PhoneSend implements MsgSender {
  msgContent: MsgContent

  constructor(msgContent: MsgContent) {
    this.msgContent = msgContent
  }

  send() {
    console.log(`phone send ${this.msgContent.content()}`)
  }
}

class EmailSend implements MsgSender {
  msgContent: MsgContent

  constructor(msgContent: MsgContent) {
    this.msgContent = msgContent
  }

  send() {
    console.log(`email send ${this.msgContent.content()}`)
  }
}

// test 此處還可以做成map結構繼續優化(略)
new PhoneSend(new WarnMsg()).send()
new PhoneSend(new ErrorMsg()).send()
new EmailSend(new WarnMsg()).send()
new EmailSend(new ErrorMsg()).send()

代理模式

應用場景

解決方案

class User{
  login(){
    console.log('user login...')
  }
}

class UserProxy extends User{
  login() {
    console.log('login before')
    super.login()
    console.log('login after')
  }
}
interface Login {
  login()
}

class User implements Login {
  login() {
    console.log('user login...')
  }
}

class UserProxy implements Login {
  user = new User()

  login() {
    console.log('login before')
    this.user.login()
    console.log('login after')
  }
}

裝飾器模式

應用場景

解決方案

Function.prototype.before = function (beforeFn) {
  return (...arg) ={
    beforeFn(...arg);
    return this(...arg);
  }
};
Function.prototype.after = function (afterFn) {
  return (...arg) ={
    const result = this(...arg);
    afterFn(...arg);
    return result;
  }
};

function ImportEvent1 {
  console.log('重要的事情說三遍 1')
}

function ImportEvent2 {
  console.log('重要的事情說三遍 2')
}

function ImportEvent3 {
  console.log('重要的事情說三遍 3')
}

// test
ImportEvent2.before(ImportEvent1).after(ImportEvent3)()

適配器模式

應用場景

解決方案

// 目標接口格式
interface ITarget {
  f1()

  f2()

  f3()
}

// 原有類與目標接口不兼容
class Origin {
  fa() {
  }

  fb() {
  }

  f3() {
  }
}

// 使用適配器來兼容
class Adaptor implements ITarget {
  origin = new Origin()

  f1() {
    this.origin.fa()
  }

  f2() {
    this.origin.fb()
  }

  f3() {
    this.origin.f3()
  }
}

享元模式

應用場景

解決方案

組合模式

應用場景

將一組對象組織成樹形結構,以表示一種 “部分 - 整體” 的層次結構。組合模式讓客戶端可以統一單個對象和組合對象的處理邏輯(遞歸遍歷)

解決方案

abstract class FileSystemNode {
  path: string

  abstract getFilesCount()

  abstract getFilesSize()
}

class FileNode extends FileSystemNode {
  constructor(path) {
    super();
    this.path = path
  }

  getFilesCount() {
    return 1
  }

  getFilesSize() {
    return require(this.path).length
  }
}

class Directory extends FileSystemNode {
  subNodes = []

  constructor(path) {
    super();
    this.path = path
  }

  getFilesCount() {
    return this.subNodes.reduce(item => item.getCount(), 0)
  }

  getFilesSize() {
    return this.subNodes.reduce(item => item.getSize(), 0)
  }
}

門面 (外觀) 模式

應用場景

行爲型設計模式

總結了一些類或對象交互的經典方式,將該行爲相關的類或對象解耦

觀察者模式

應用場景

解決方案

// 目標對象
class Subject {
  observerList: Observer[]

  constructor() {
    this.observerList = [];
  }

  addObserver(observer) {
    this.observerList.push(observer);
  }

  notify() {
    this.observerList.forEach((observer) ={
      observer.update();
    });
  }
}

// 觀察者
class Observer {
  cb: Function

  constructor(cb: Function) {
    if (typeof cb === "function") {
      this.cb = cb;
    } else {
      throw new Error("Observer構造器必須傳入函數類型!");
    }
  }

  update() {
    this.cb();
  }
}


// test
const observerCallback = function () {
  console.log("我被通知了");
};
const observer = new Observer(observerCallback);
const subject = new Subject();
subject.addObserver(observer);
subject.notify();

模板模式

應用場景

解決方案

abstract class Drinks {
  firstStep() {
    console.log('燒開水')
  }

  abstract secondStep()

  thirdStep() {
    console.log('倒入杯子')
  }

  abstract fourthStep()

  drink() {
    this.firstStep()
    this.secondStep()
    this.thirdStep()
    this.fourthStep()
  }
}

class Tea extends Drinks {
  secondStep() {
    console.log('浸泡茶葉')
  }

  fourthStep() {
    console.log('加檸檬')
  }
}

class Coffee extends Drinks {
  secondStep() {
    console.log('沖泡咖啡')
  }

  fourthStep() {
    console.log('加糖')
  }
}

// test
const tea = new Tea()
tea.drink()
const coffee = new Coffee()
coffee.drink()

策略模式

應用場景

解決方案

enum StrategyType {
  S,
  A,
  B
}

const strategyFn = {
  'S'function (salary: number) {
    return salary * 4
  },
  'A'function (salary: number) {
    return salary * 3
  },
  'B'function (salary: number) {
    return salary * 2
  }
}

const calculateBonus = function (level: StrategyType, salary: number) {
  return strategyFn[level](salary)
}

calculateBonus(StrategyType.A, 10000) // 30000

職責鏈模式

應用場景

解決方案

const order500 = function (orderType, pay, stock) {
  if (orderType === 1 && pay === true) {
    console.log("500 元定金預購, 得到 100 元優惠券");
    return true;
  } else {
    return false;
  }
};

const order200 = function (orderType, pay, stock) {
  if (orderType === 2 && pay === true) {
    console.log("200 元定金預購, 得到 50 元優惠券");
    return true;
  } else {
    return false;
  }
};

const orderCommon = function (orderType, pay, stock) {
  if ((orderType === 3 || !pay) && stock > 0) {
    console.log("普通購買, 無優惠券");
    return true;
  } else {
    console.log("庫存不夠, 無法購買");
    return false;
  }
};

class chain {
  fn: Function
  nextFn: Function

  constructor(fn: Function) {
    this.fn = fn;
    this.nextFn = null;
  }

  setNext(nextFn) {
    this.nextFn = nextFn
  }

  init(...arguments) {
    const result = this.fn(...arguments);
    if (!result && this.nextFn) {
      this.nextFn.init(...arguments);
    }
  }
}

const order500New = new chain(order500);
const order200New = new chain(order200);
const orderCommonNew = new chain(orderCommon);

order500New.setNext(order200New);
order200New.setNext(orderCommonNew);

order500New.init(3, true, 500); // 普通購買, 無優惠券

狀態模式

應用場景

解決方案

class weakLight {
  light: Light

  constructor(light: Light) {
    this.light = light
  }

  press() {
    console.log('打開強光')
    this.light.setState(this.light.strongLight)
  }
}

class strongLight {
  light: Light

  constructor(light: Light) {
    this.light = light
  }

  press() {
    console.log('關燈')
    this.light.setState(this.light.offLight)
  }
}

class offLight {
  light: Light

  constructor(light: Light) {
    this.light = light
  }

  press() {
    console.log('打開弱光')
    this.light.setState(this.light.weakLight)
  }
}


class Light {
  weakLight: weakLight
  strongLight: strongLight
  offLight: offLight
  currentState: offLight | weakLight | strongLight //當前狀態: 默認關燈狀態

  constructor() {
    this.weakLight = new weakLight(this)
    this.strongLight = new strongLight(this)
    this.offLight = new offLight(this)
    this.currentState = this.offLight
  }

  press() {
    this.currentState.press()
  }

  setState(state) {
    this.currentState = state
  }
}

// test
const light = new Light()
light.press()
light.press()
light.press()
light.press()
light.press()
light.press()

迭代器模式

應用場景

訪問者模式

應用場景

備忘錄模式

應用場景

解決方案

class Programmer {
  age: number
  username: string
  color: string
  area: string

  constructor(p) {
    this.age = p.age
    this.username = p.username
    this.color = p.color
    this.area = p.area
  }

  // 創建一個快照
  createSnapshot() {
    return {
      age: this.age,
      username: this.username,
      color: this.color,
      area: this.area
    }
  }

  // 通過快照恢復對象狀態
  restoreSnapshot(snapshot: Programmer) {
    this.age = snapshot.age
    this.username = snapshot.username
    this.color = snapshot.color
    this.area = snapshot.area
  }
}

命令模式

應用場景

解決方案

interface Command {
  execute()
}

class closeDoorCommand implements Command {
  execute() {
    console.log('close door');
  }
}

class openPcCommand implements Command {
  execute() {
    console.log('open pc');
  }
}

class openQQCommand implements Command {
  execute() {
    console.log('login qq');
  }
}

class CommandManager {
  commandList: Command[] = []

  addCommand(command: Command) {
    this.commandList.push(command)
  }

  execute() {
    this.commandList.forEach(command ={
      command.execute()
    })
  }
}

//test
const commandManager = new CommandManager();
commandManager.addCommand(new closeDoorCommand());
commandManager.addCommand(new openPcCommand());
commandManager.addCommand(new openQQCommand());
commandManager.execute();

解釋器模式

應用場景

給定一個語言,定義它的文法表示,並定義一個解釋器,這個解釋器使用該標識來解釋語言中的句子

中介模式

應用場景

如何評價代碼的質量?

怎樣形成長期記憶?

注意事項

知識是死的,而代碼是活的,不要用固化的設計模式實現硬套在活的業務邏輯裏。
能學以致用是我們的學習目標,但是如果寫出來的代碼同組的其他人都看不懂,更加影響項目的可維護性和開發效率。所以我們可以少用慎用,但是我們必須掌握其思想。
牢牢掌握設計模式,拿去面試、面試別人、組內分享還是可以震懾羣雄的,啊哈哈哈

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/I65GKW8Jp0T-CJCU6qcDxA