Typescript玩轉設計模式 之 對象行爲型模式(下)

做者簡介 joey 螞蟻金服·數據體驗技術團隊html

本文是typescript設計模式系列文章的最後一篇,介紹了最後5個對象行爲型的設計模式~java

  • 觀察者模式
  • 狀態模式
  • 策略模式
  • 模板模式
  • 訪問者模式

Observer(觀察者)

意圖

定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知並被自動更新。git

結構

觀察者模式包含如下角色:github

  • Subject(目標):目標又稱爲主題,它是指被觀察的對象。在目標中定義了一個觀察者集合,一個觀察目標能夠接受任意數量的觀察者來觀察,它提供一系列方法來增長和刪除觀察者對象,同時它定義了通知方法notify()。目標類能夠是接口,也能夠是抽象類或具體類。
  • ConcreteSubject(具體目標):具體目標是目標類的子類,一般它包含有常常發生改變的數據,當它的狀態發生改變時,向它的各個觀察者發出通知;同時它還實現了在目標類中定義的抽象業務邏輯方法(若是有的話)。若是無須擴展目標類,則具體目標類能夠省略。
  • Observer(觀察者):觀察者將對觀察目標的改變作出反應,觀察者通常定義爲接口,該接口聲明瞭更新數據的方法update(),所以又稱爲抽象觀察者。
  • ConcreteObserver(具體觀察者):在具體觀察者中維護一個指向具體目標對象的引用,它存儲具體觀察者的有關狀態,這些狀態須要和具體目標的狀態保持一致;它實現了在抽象觀察者Observer中定義的update()方法。一般在實現時,能夠調用具體目標類的attach()方法將本身添加到目標類的集合中或經過detach()方法將本身從目標類的集合中刪除。

示例

推模型

目標向觀察者發送關於改變的「詳細信息」,而無論它們須要與否。由目標維護觀察者。算法

// 場景:顧客點菜後,服務員記下顧客的信息,菜作好後廣播通知顧客領取

  // 觀察者基類
  class Observer {
    take(msg: string): void {}
  }

  // 目標基類
  class Subject {
    set: Set<Observer> = new Set();
    // 註冊回調
    add(observer: Observer): void {
      this.set.add(observer);
    }
    // 註銷回調
    remove(observer: Observer): void {
      this.set.delete(observer);
    }
    // 觸發全部已註冊的回調
    notify(msg: string): void {
      this.set.forEach(observer => {
        observer.take(msg);
      });
    }
  }

  // 具體目標,服務員類
  class Waiter extends Subject {
    // 菜作完後通知全部註冊了的顧客
    ready(): void {
      this.notify('ready');
    }
  }

  // 具體觀察者,顧客類
  class Client extends Observer {
    name: string;
    // 初始化時將自身註冊到目標,以便接收通知
    constructor(name: string, waiter: Waiter) {
      super();
      this.name = name;
      waiter.add(this);
    }
    take(msg: string) {
      console.log(`顧客 ${this.name} 收到了消息顯示狀態是<${msg}>, 到吧檯領取了菜`);
    }
  }

  function observerPushDemo() {
    const waiter = new Waiter();
    // 顧客點菜後,等待服務員通知
    const bob = new Client('Bob', waiter);
    const mick = new Client('Mick', waiter);
    // 菜準備好後,服務員廣播通知顧客能夠到吧檯領取了
    waiter.ready();
  }
複製代碼

拉模型

目標除了「最小通知」外什麼也不送出,而在此以後由觀察者顯式地向目標詢問細節。觀察者裏維護了目標對象。typescript

// 場景:顧客點菜後,收到通知從服務員處詢問詳細信息

  // 觀察者基類
  class Observer {
    take(subject: Subject): void {}
  }

  // 目標基類
  class Subject {
    set: Set<Observer> = new Set();
    // 註冊回調
    add(observer: Observer): void {
      this.set.add(observer);
    }
    // 註銷回調
    remove(observer: Observer): void {
      this.set.delete(observer);
    }
    // 觸發全部已註冊的回調
    notify(): void {
      this.set.forEach(observer => {
        observer.take(this);
      });
    }
  }

  // 具體目標,服務員類
  class Waiter extends Subject {
    status = 'doing';
    // 與推模式的區別是,只發送通知,不發送詳細數據
    ready(): void {
      this.status = 'ready';
      this.notify();
    }
    // 提供訪問詳細數據接口,讓觀察者訪問詳細數據
    getStatus(): string {
      return this.status;
    }
  }

  // 具體觀察者,顧客類
  class Client extends Observer {
    name: string;
    // 初始化時將自身註冊到目標,以便接收通知
    constructor(name: string, waiter: Waiter) {
      super();
      this.name = name;
      waiter.add(this);
    }
    // 與推模式的區別是,收到通知後,沒有數據傳入,須要從目標裏讀取
    take(waiter: Waiter) {
      const msg = waiter.getStatus();
      console.log(`顧客 ${this.name} 收到通知,詢問服務員後發現狀態是 <${msg}> 後領取了菜`);
    }
  }

  function observerPushDemo() {
    const waiter = new Waiter();
    // 顧客點菜
    const bob = new Client('Bob', waiter);
    const mick = new Client('Mick', waiter);
    // 菜準備完後,服務員通知了下全部顧客狀態改變了,但沒有發送內容出去,須要顧客再詢問一下服務員才知道最新狀態
    waiter.ready();
  }
複製代碼

適用場景

  • 當一個抽象模型有兩個方面,其中一個方面依賴於另外一方面。將這二者封裝在獨立的對象中以使它們能夠各自獨立地改變和複用;
  • 當一個對象的改變須要同時改變其餘對象,而不知道具體有多少對象有待改變;
  • 當一個對象必須通知其餘對象,而它又不能假定其餘對象是誰。換言之,你不但願這些對象是緊密耦合的;

優勢

  • 目標和觀察者間的抽象耦合。一個目標所知道的僅僅是它有一系列觀察者,每一個都符合抽象的Observer類的簡單接口。目標不須要知道任何一個觀察者屬於哪個具體的類。
  • 支持廣播通訊。目標發現的通知不須要指定它的接收者。目標對象並不關心有多少觀察者對象對本身感興趣,惟一的職責就是通知已註冊的各觀察者。

缺點

  • 意外的更新。由於一個觀察者並不知道其餘觀察者的存在,它可能對改變目標的最終代價一無所知。在目標上一個看似無害的操做可能會引發一系列對觀察者以及依賴於這些觀察者的那些對象的更新。由此引起的問題經常難以追蹤。

相關模式

  • Mediator:經過封裝複雜的更新語義,ChangeManager充當目標和觀察者之間的中介者。
  • Singleton:ChangeManager可以使用單例模式來保證它是惟一的而且是可全局訪問的。

State(狀態)

意圖

容許一個對象在其內部狀態改變時改變它的行爲。對象看起來彷佛修改了它的類。設計模式

結構

狀態模式包含如下角色:bash

  • Context(環境類):環境類又稱爲上下文類,它是擁有多種狀態的對象。因爲環境類的狀態存在多樣性且在不一樣狀態下對象的行爲有所不一樣,所以將狀態獨立出去造成單獨的狀態類。在環境類中維護一個抽象狀態類State的實例,這個實例定義當前狀態,在具體實現時,它是一個State子類的對象。
  • State(抽象狀態類):它用於定義一個接口以封裝與環境類的一個特定狀態相關的行爲,在抽象狀態類中聲明瞭各類不一樣狀態對應的方法,而在其子類中實現類這些方法,因爲不一樣狀態下對象的行爲可能不一樣,所以在不一樣子類中方法的實現可能存在不一樣,相同的方法能夠寫在抽象狀態類中。
  • ConcreteState(具體狀態類):它是抽象狀態類的子類,每個子類實現一個與環境類的一個狀態相關的行爲,每個具體狀態類對應環境的一個具體狀態,不一樣的具體狀態類其行爲有所不一樣。

示例

// 帳戶有幾種狀態:正常,透支,受限

  // 帳戶類,表明狀態模式中的環境
  class Account {
    private name: string;
    private state: State;
    // 餘額
    private balance = 0;
    // 初始時爲正常狀態
    constructor(name: string) {
      this.name = name;
      this.state = new NormalState(this);
      console.log(`用戶 ${this.name} 開戶,餘額爲 ${this.balance}`);
      console.log('--------');
    }
    getBalance(): number {
      return this.balance;
    }
    setBalance(balance: number) {
      this.balance = balance;
    }
    setState(state: State) {
      this.state = state;
    }
    // 存款
    deposit(amount: number) {
      this.state.deposit(amount);
      console.log(`存款 ${amount}`);
      console.log(`餘額爲 ${this.balance}`);
      console.log(`帳戶狀態爲 ${this.state.getName()}`);
      console.log('--------');
    }
    // 取款
    withdraw(amount: number) {
      this.state.withdraw(amount);
      console.log(`取款 ${amount}`);
      console.log(`餘額爲 ${this.balance}`);
      console.log(`帳戶狀態爲 ${this.state.getName()}`);
      console.log('--------');
    }
    // 結算利息
    computeInterest() {
      this.state.computeInterest();
    }
  }

  // 狀態抽象類
  abstract class State {
    private name: string;
    protected acc: Account;
    constructor(name: string) {
      this.name = name;
    }
    getName() {
      return this.name;
    }
    abstract deposit(amount: number);  
    abstract withdraw(amount: number);  
    abstract computeInterest();  
    abstract stateCheck();
  }

  // 正常狀態類
  class NormalState extends State {
    acc: Account;
    constructor(acc: Account) {
      super('正常');
      this.acc = acc;
    }
    deposit(amount: number) {
      this.acc.setBalance(this.acc.getBalance() + amount);
      this.stateCheck();
    }
    withdraw(amount: number) {
      this.acc.setBalance(this.acc.getBalance() - amount);  
      this.stateCheck();
    }
    computeInterest() {
      console.log('正常狀態,無須支付利息');
    }
    // 狀態轉換
    stateCheck() {
      if (this.acc.getBalance() > -2000 && this.acc.getBalance() <= 0) {  
          this.acc.setState(new OverdraftState(this.acc));  
      } else if (this.acc.getBalance() == -2000) {  
          this.acc.setState(new RestrictedState(this.acc));  
      } else if (this.acc.getBalance() < -2000) {  
          console.log('操做受限');  
      }
    }
  }

  // 透支狀態
  class OverdraftState extends State {
    acc: Account;
    constructor(acc: Account) {
      super('透支');
      this.acc = acc;
    }
    deposit(amount: number) {
      this.acc.setBalance(this.acc.getBalance() + amount);
      this.stateCheck();
    }
    withdraw(amount: number) {
      this.acc.setBalance(this.acc.getBalance() - amount);  
      this.stateCheck();
    }
    computeInterest() {
      console.log('計算利息');
    }
    // 狀態轉換
    stateCheck() {
      if (this.acc.getBalance() > 0) {
        this.acc.setState(new NormalState(this.acc));
      } else if (this.acc.getBalance() == -2000) {
        this.acc.setState(new RestrictedState(this.acc));
      } else if (this.acc.getBalance() < -2000) {
        console.log('操做受限');
      }
    }
  }

  // 受限狀態
  class RestrictedState extends State {
    acc: Account;
    constructor(acc: Account) {
      super('受限');
      this.acc = acc;
    }
    deposit(amount: number) {
      this.acc.setBalance(this.acc.getBalance() + amount);
      this.stateCheck();
    }
    withdraw(ammount: number) {
      console.log('帳號受限,取款失敗');
    }
    computeInterest() {
      console.log('計算利息');
    }
    // 狀態轉換
    stateCheck() {
      if (this.acc.getBalance() > 0) {  
        this.acc.setState(new NormalState(this.acc));  
      } else if (this.acc.getBalance() > -2000) {  
        this.acc.setState(new OverdraftState(this.acc));  
      }
    }
  }

  function stateDemo() {
    const acc = new Account('Bob');
    acc.deposit(1000);  
    acc.withdraw(2000);  
    acc.deposit(3000);  
    acc.withdraw(4000);  
    acc.withdraw(1000);  
    acc.computeInterest(); 
  }
複製代碼

適用場景

  • 一個對象的行爲取決於它的狀態,而且它必須在運行時刻根據狀態改變它的行爲;
  • 一個操做中含有龐大的多分支的條件語句,且這些分支依賴於該對象的狀態。這個狀態一般用一個或多個枚舉常量表示。有多個操做包含這一相同的條件結構。狀態模式將每個條件分支放入一個獨立的類中。這使得你能夠根據對象自身的狀況將對象的狀態做爲一個對象,這一對象能夠不依賴於其餘對象而獨立變化。

優勢

  • 封裝了狀態的轉換規則,在狀態模式中能夠將狀態的轉換代碼封裝在環境類或者具體狀態類中,能夠對狀態轉換代碼進行集中管理,而不是分散在一個個業務方法中。
  • 將全部與某個狀態有關的行爲放到一個類中,只須要注入一個不一樣的狀態對象便可使環境對象擁有不一樣的行爲。
  • 容許狀態轉換邏輯與狀態對象合成一體,而不是提供一個巨大的條件語句塊,狀態模式可讓咱們避免使用龐大的條件語句來將業務方法和狀態轉換代碼交織在一塊兒。
  • 可讓多個環境對象共享一個狀態對象,從而減小系統中對象的個數。

缺點

  • 狀態模式的使用必然會增長系統中類和對象的個數,致使系統運行開銷增大。
  • 狀態模式的結構與實現都較爲複雜,若是使用不當將致使程序結構和代碼的混亂,增長系統設計的難度。
  • 狀態模式對「開閉原則」的支持並不太好,增長新的狀態類須要修改那些負責狀態轉換的源代碼,不然沒法轉換到新增狀態;並且修改某個狀態類的行爲也需修改對應類的源代碼。

相關模式

  • 享元模式解釋了什麼時候以及怎樣共享狀態對象;
  • 狀態對象一般是單例;

Strategy(策略模式)

意圖

定義一系列的算法,把它們一個個封裝起來,而且使它們可相互替換。本模式使得算法可獨立於使用它的客戶而變化。數據結構

結構

策略模式包含如下角色:框架

  • Context(環境類):環境類是使用算法的角色,它在解決某個問題(即實現某個方法)時能夠採用多種策略。在環境類中維持一個對抽象策略類的引用實例,用於定義所採用的策略。
  • Strategy(抽象策略類):它爲所支持的算法聲明瞭抽象方法,是全部策略類的父類,它能夠是抽象類或具體類,也能夠是接口。環境類經過抽象策略類中聲明的方法在運行時調用具體策略類中實現的算法。
  • ConcreteStrategy(具體策略類):它實現了在抽象策略類中聲明的算法,在運行時,具體策略類將覆蓋在環境類中定義的抽象策略類對象,使用一種具體的算法實現某個業務處理。

示例

// 火車票類:環境類
class TrainTicket {
  private price: number;
  private discount: Discount;
  constructor(price: number) {
    this.price = price;
  }
  setDiscount(discount: Discount) {
    this.discount = discount;
  }
  getPrice(): number {
    return this.discount.calculate(this.price);
  }
}

// 折扣接口
interface Discount {
  calculate(price: number): number;
}

// 學生票折扣
class StudentDiscount implements Discount {
  calculate(price: number): number {
    console.log('學生票打7折');
    return price * 0.7;
  }
}

// 兒童票折扣
class ChildDiscount implements Discount {
  calculate(price: number): number {
    console.log('兒童票打5折');
    return price * 0.5;
  }
}

// 軍人票折扣
class SoldierDiscount implements Discount {
  calculate(price: number): number {
    console.log('軍人免票');
    return 0;
  }
}

function strategyDemo() {
  const ticket: TrainTicket = new TrainTicket(100);

  // 從環境中獲取到身份信息,而後根據身份信息獲取折扣策略
  const discount: Discount = getIdentityDiscount();
  // 注入折扣策略對象
  ticket.setDiscount(discount);
  // 根據策略對象獲取票價
  console.log(ticket.getPrice());
}
複製代碼

適用場景

  • 許多相關的類僅僅是行爲有異。「策略」提供了一種用多個行爲中的一個行爲來配置一個類的方法。
  • 須要使用一個算法的不一樣變體。
  • 算法使用客戶不該該知道的數據。可以使用策略模式以免暴露覆雜的、與算法有關的數據結構。
  • 一個類定義了多種行爲,而且這些行爲在這個類的操做中以多個條件語句的形式出現。將相關的條件分支移入它們各自的策略類中以代替這些條件語句。

優勢

  • 提供了對「開閉原則」的完美支持,用戶能夠在不修改原有系統的基礎上選擇算法或行爲,也能夠靈活地增長新的算法或行爲。
  • 提供了管理相關的算法族的辦法。策略類的等級結構定義了一個算法或行爲族,恰當使用繼承能夠把公共的代碼移到抽象策略類中,從而避免重複的代碼。
  • 提供了一種能夠替換繼承關係的辦法。若是不使用策略模式,那麼使用算法的環境類就可能會有一些子類,每個子類提供一種不一樣的算法。可是,這樣一來算法的使用就和算法自己混在一塊兒,不符合「單一職責原則」,決定使用哪種算法的邏輯和該算法自己混合在一塊兒,從而不可能再獨立演化;並且使用繼承沒法實現算法或行爲在程序運行時的動態切換。
  • 使用策略模式能夠避免多重條件選擇語句。多重條件選擇語句不易維護,它把採起哪種算法或行爲的邏輯與算法或行爲自己的實現邏輯混合在一塊兒,將它們所有硬編碼在一個龐大的多重條件選擇語句中,比直接繼承環境類的辦法還要原始和落後。
  • 提供了一種算法的複用機制,因爲將算法單獨提取出來封裝在策略類中,所以不一樣的環境類能夠方便地複用這些策略類。

缺點

  • 客戶端必須知道全部的策略類,並自行決定使用哪個策略類。這就意味着客戶端必須理解這些算法的區別,以便適時選擇恰當的算法。換言之,策略模式只適用於客戶端知道全部的算法或行爲的狀況。
  • 策略模式將形成系統產生不少具體策略類,任何細小的變化都將致使系統要增長一個新的具體策略類。
  • 沒法同時在客戶端使用多個策略類,也就是說,在使用策略模式時,客戶端每次只能使用一個策略類,不支持使用一個策略類完成部分功能後再使用另外一個策略類來完成剩餘功能的狀況。

相關模式

  • 享元: 策略對象常常是很好的輕量級對象。

Template Method(模板方法)

意圖

定義一個操做中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟。

結構

模板方法包含如下角色:

  • AbstractClass(抽象類):在抽象類中定義了一系列基本操做(PrimitiveOperations),這些基本操做能夠是具體的,也能夠是抽象的,每個基本操做對應算法的一個步驟,在其子類中能夠重定義或實現這些步驟。同時,在抽象類中實現了一個模板方法(Template Method),用於定義一個算法的框架,模板方法不只能夠調用在抽象類中實現的基本方法,也能夠調用在抽象類的子類中實現的基本方法,還能夠調用其餘對象中的方法。
  • ConcreteClass(具體子類):它是抽象類的子類,用於實如今父類中聲明的抽象基本操做以完成子類特定算法的步驟,也能夠覆蓋在父類中已經實現的具體基本操做。

示例

模板方法是基於繼承的一種模式。

下面是一個組件渲染的例子,模擬React組件渲染流程。

// 組件基類
class Component {
  // 模板方法,把組件渲染的流程定義好
  setup() {
    this.componentWillMount();
    this.doRender();
    this.componentDidMount();
  }
  private doRender() {
    // 作實際的渲染工做
  }
  componentWillMount() {}
  componentDidMount() {}
}

class ComponentA extends Component {
  componentWillMount() {
    console.log('A組件即將被渲染');
  }
  componentDidMount() {
    console.log('A組件渲染完成');
  }
}

class ComponentB extends Component {
  componentWillMount() {
    console.log('B組件即將被渲染');
  }
  componentDidMount() {
    console.log('B組件渲染完成');
  }
}

// 渲染A和B組件,生命週期的流程都是相同的,已經在模板方法裏定義好了的
function templateMethodDemo() {
  const compA = new ComponentA();
  compA.setup();

  const compB = new ComponentB();
  compB.setup();
}
複製代碼

適用場景

  • 須要控制流程的邏輯順序時。模板方法模式普遍應用於框架設計中,以確保經過父類來控制處理流程的邏輯順序(如框架的初始化,測試流程的設置等)

優勢

  • 在父類中形式化地定義一個算法,而由它的子類來實現細節的處理,在子類實現詳細的處理算法時並不會改變算法中步驟的執行次序。
  • 模板方法模式是一種代碼複用技術,它在類庫設計中尤其重要,它提取了類庫中的公共行爲,將公共行爲放在父類中,而經過其子類來實現不一樣的行爲,它鼓勵咱們恰當使用繼承來實現代碼複用。
  • 可實現一種反向控制結構,經過子類覆蓋父類的鉤子方法來決定某一特定步驟是否須要執行。
  • 在模板方法模式中能夠經過子類來覆蓋父類的基本方法,不一樣的子類能夠提供基本方法的不一樣實現,更換和增長新的子類很方便,符合單一職責原則和開閉原則。

缺點

  • 須要爲每個基本方法的不一樣實現提供一個子類,若是父類中可變的基本方法太多,將會致使類的個數增長,系統更加龐大,設計也更加抽象,此時,可結合橋接模式來進行設計。

相關模式

  • 工廠方法: 常被模板方法調用。
  • 策略模式:模板方法使用繼承來改變算法的一部分。策略模式使用委託來改變整個算法。

訪問者模式

意圖

提供一個做用於某對象結構中的各元素的操做表示,它使咱們能夠在不改變各元素的類的前提下定義做用於這些元素的新操做。

結構

訪問者模式包含如下角色:

  • Vistor(抽象訪問者):抽象訪問者爲對象結構中每個具體元素類ConcreteElement聲明一個訪問操做,從這個操做的名稱或參數類型能夠清楚知道須要訪問的具體元素的類型,具體訪問者須要實現這些操做方法,定義對這些元素的訪問操做。
  • ConcreteVisitor(具體訪問者):具體訪問者實現了每一個由抽象訪問者聲明的操做,每個操做用於訪問對象結構中一種類型的元素。
  • Element(抽象元素):抽象元素通常是抽象類或者接口,它定義一個accept()方法,該方法一般以一個抽象訪問者做爲參數。【稍後將介紹爲何要這樣設計。】
  • ConcreteElement(具體元素):具體元素實現了accept()方法,在accept()方法中調用訪問者的訪問方法以便完成對一個元素的操做。
  • ObjectStructure(對象結構):對象結構是一個元素的集合,它用於存放元素對象,而且提供了遍歷其內部元素的方法。它能夠結合組合模式來實現,也能夠是一個簡單的集合對象,如一個List對象或一個Set對象。

示例

一個公司有兩種員工,正式工和臨時工,他們有不一樣的工時和薪酬結算方法。

// 員工接口
  interface Employee {
    accept(handler: Department): void;
  }

  // 全職員工類
  class FulltimeEmployee implements Employee {
    private name = '';
    // 全職員工按週薪計算薪酬
    private weeklyWage = 0;
    // 工做時長
    private workTime = 0;
    constructor(name: string, weeklyWage: number, workTime: number) {
      this.name = name;
      this.weeklyWage = weeklyWage;
      this.workTime = workTime;
    }
    getName(): string {
      return this.name;
    }
    getWeeklyWage(): number {
      return this.weeklyWage;
    }
    getWorkTime(): number {
      return this.workTime;
    }
    // 實現接口,調用訪問者處理全職員工的方法
    accept(handler: Department) {
      handler.visitFulltime(this);
    }
  }

  // 臨時員工類
  class ParttimeEmployee implements Employee {
    private name = '';
    // 臨時員工按時薪計算薪酬
    private hourWage = 0;
    // 工做時長
    private workTime = 0;
    constructor(name: string, hourWage: number, workTime: number) {
      this.name = name;
      this.hourWage = hourWage;
      this.workTime = workTime;
    }
    getName(): string {
      return this.name;
    }
    getHourWage(): number {
      return this.hourWage;
    }
    getWorkTime(): number {
      return this.workTime;
    }
    // 實現接口,調用訪問者處理臨時工的方法
    accept(handler: Department) {
      handler.visitParttime(this);
    }
  }

  // 部門接口
  interface Department {
    visitFulltime(employee: FulltimeEmployee): void;
    visitParttime(employee: ParttimeEmployee): void;
  }

  // 具體訪問者——財務部,結算薪酬實現部門接口
  class FADepartment implements Department {
    // 全職員工薪酬計算方式
    visitFulltime(employee: FulltimeEmployee) {
      const name: string = employee.getName();
      let workTime: number = employee.getWorkTime();
      let weekWage: number = employee.getWeeklyWage();
      const WEEK_WORK_TIME = 40;
      if (workTime > WEEK_WORK_TIME) {
        // 計算加班工資
        const OVER_TIME_WAGE = 100;
        weekWage = weekWage + (workTime - WEEK_WORK_TIME) * OVER_TIME_WAGE;
      } else if (workTime < WEEK_WORK_TIME) {
        if (workTime < 0) {
          workTime = 0;
        }
        // 扣款
        const CUT_PAYMENT = 80;
        weekWage = weekWage - (WEEK_WORK_TIME - workTime) * CUT_PAYMENT;
      }
      console.log(`正式員工 ${name} 實際工資爲:${weekWage}`);
    }
    // 臨時工薪酬計算方式
    visitParttime(employee: ParttimeEmployee) {
      const name = employee.getName();
      const hourWage = employee.getHourWage();
      const workTime = employee.getWorkTime();
      console.log(`臨時工 ${name} 實際工資爲:${hourWage * workTime}`);
    }
  }

  // 具體訪問者——人力資源部,結算工做時間,實現部門接口
  class HRDepartment implements Department {
    // 全職員工工做時間報告
    visitFulltime(employee: FulltimeEmployee) {
      const name: string = employee.getName();
      let workTime: number = employee.getWorkTime();
      // 實際工做時間報告
      let report = `正式員工 ${name} 實際工做時間爲 ${workTime} 小時`;
      const WEEK_WORK_TIME = 40;
      if (workTime > WEEK_WORK_TIME) {
        // 加班時間報告
        report = `${report},加班 ${WEEK_WORK_TIME - workTime} 小時`;
      } else if (workTime < WEEK_WORK_TIME) {
        if (workTime < 0) {
          workTime = 0;
        }
        // 請假時間報告
        report = `${report},請假 ${WEEK_WORK_TIME - workTime} 小時`;
      }
      console.log(report);
    }
    // 臨時工工做時間報告
    visitParttime(employee: ParttimeEmployee) {
      const name: string = employee.getName();
      const workTime: number = employee.getWorkTime();
      console.log(`臨時工 ${name} 實際工做時間爲 ${workTime} 小時`);
    }
  }

  // 員工集合類
  class EmployeeList {
    list: Array<Employee> = [];
    add(employee: Employee) {
      this.list.push(employee);
    }
    // 遍歷員工集合中的每個對象
    accept(handler: Department) {
      this.list.forEach((employee: Employee) => {
        employee.accept(handler);
      });
    }
  }

  function visitorDemo() {
    const list: EmployeeList = new EmployeeList();
    const full1 = new FulltimeEmployee('Bob', 3000, 45);
    const full2 = new FulltimeEmployee('Mikel', 2000, 35);
    const full3 = new FulltimeEmployee('Joe', 4000, 40);
    const part1 = new ParttimeEmployee('Lili', 80, 20);
    const part2 = new ParttimeEmployee('Lucy', 60, 15);

    list.add(full1);
    list.add(full2);
    list.add(full3);
    list.add(part1);
    list.add(part2);

    // 財務部計算薪酬
    const faHandler = new FADepartment();
    list.accept(faHandler);

    // 人力資源部出工做報告
    const hrHandler = new HRDepartment();
    list.accept(hrHandler);
  }
複製代碼

適用場景

  • 一個對象結構包含多個類型的對象,但願對這些對象實施一些依賴其具體類型的操做。在訪問者中針對每一種具體的類型都提供了一個訪問操做,不一樣類型的對象能夠有不一樣的訪問操做。
  • 須要對一個對象結構中的對象進行不少不一樣的而且不相關的操做,而須要避免讓這些操做「污染」這些對象的類,也不但願在增長新操做時修改這些類。訪問者模式使得咱們能夠將相關的訪問操做集中起來定義在訪問者類中,對象結構能夠被多個不一樣的訪問者類所使用,將對象自己與對象的訪問操做分離。
  • 對象結構中對象對應的類不多改變,但常常須要在此對象結構上定義新的操做。

優勢

  • 增長新的訪問操做很方便。使用訪問者模式,增長新的訪問操做就意味着增長一個新的具體訪問者類,實現簡單,無須修改源代碼,符合「開閉原則」。
  • 將有關元素對象的訪問行爲集中到一個訪問者對象中,而不是分散在一個個的元素類中。類的職責更加清晰,有利於對象結構中元素對象的複用,相同的對象結構能夠供多個不一樣的訪問者訪問。
  • 讓用戶可以在不修改現有元素類層次結構的狀況下,定義做用於該層次結構的操做。

缺點

  • 增長新的元素類很困難。在訪問者模式中,每增長一個新的元素類都意味着要在抽象訪問者角色中增長一個新的抽象操做,並在每個具體訪問者類中增長相應的具體操做,這違背了「開閉原則」的要求。
  • 破壞封裝。訪問者模式要求訪問者對象訪問並調用每個元素對象的操做,這意味着元素對象有時候必須暴露一些本身的內部操做和內部狀態,不然沒法供訪問者訪問。

相關模式

  • 組合模式:訪問者能夠用於對一個由組合模式定義的對象結構進行操做;

參考文檔

本文介紹了最後5種對象行爲型模式,多謝你們對於系列文章的支持~對團隊感興趣的同窗能夠關注專欄或者發送簡歷至'tao.qit####alibaba-inc.com'.replace('####', '@'),歡迎有志之士加入~

原文地址:github.com/ProtoTeam/b…

相關文章
相關標籤/搜索