iT邦幫忙

0

【SOLID】Liskov替換原則(子類替換)

  • 分享至 

  • xImage
  •  

重點 — 將子類替換掉,也不影響原程式運作

  • 繼承原class的子類,在程式中被替換為子類,也不會影響其運作。
  • 常用的情況是在「替換呼叫模組」,將類別(class)換成其他子類,程式也可以正常運行。

舉例 & 限制

參考 設計模式 禪的解說,因應此原則,父子 class 會有一些「限制」:參數輸入和回傳型別

  • 參數輸入限制:父 class 的參數「範圍」,要比子 class 的參數還小。
    • e.g. 父 class 只能接受 string ,而子 class 倒是可以接受 string | number,這樣在主程序傳入 string ,替換為子 class 就沒問題
    • 反過來說,如果父 class 是 string | number,子 class 只能接受 string,那麼在主程序傳入 number ,替換為子 class 就型別報錯,出事情了!
  • 回傳型別限制:父 class 的回傳「範圍」,要比子 class 的參數還大。
    • e.g. 這就比較直覺了,父 class 回傳 string | number,替換為回傳 string 的子 class ,不會有問題。
    • *即便是 JavaScript 這種沒型別的,也需要遵守此規則(但沒型別檢查,因此容易違反!)

舉例實作

  • class

    • 子類

      type State = Record<string, any>;
      
      interface BasicState {
          state: State;
      
          getState(): State;
      }
      
      interface MemberStateObj extends State {
          account: string;
      }
      class MemberState implements BasicState {
          state: MemberStateObj;
      
          constructor(state: MemberStateObj) {
              this.state = state;
          }
      
          getState() {
              return this.state;
          }
      }
      
      interface OrderStateObj extends State {
          items: any[];
          transportation: number;
      }
      class OrderState implements BasicState {
          state: OrderStateObj;
      
          constructor(state: OrderStateObj) {
              this.state = state;
          }
      
          getState() {
              return this.state;
          }
      }
      
    • 使用子類的class

      class StateManager {
          stateHolder: BasicState;
      
          constructor(stateHolder: BasicState) {
              this.stateHolder = stateHolder;
          }
      
          getStateKeys() {
              return Object.keys(this.stateHolder.getState());
          }
      }
      
      const stateManager = new StateManager(
          new MemberState({
              account: 'aaa111',
          })
      );
      stateManager.getStateKeys(); // [ 'account' ]
      
      stateManager.stateHolder = new OrderState({
          items: [],
          transportation: 20,
      });
      stateManager.getStateKeys(); // [ 'items', 'transportation' ]
      
  • function

    • 子類function

      function calc(calcFn: Function, ...nums: number[]): number {
          return calcFn(nums);
      }
      
      function calcSumV2(...nums: number[]): number {
          return calc((_nums: number[]) => _nums.reduce((p, n) => p + n, 0), ...nums);
      }
      
      function calcMutiply(...nums: number[]): number {
          return calc((_nums: number[]) => _nums.reduce((p, n) => p * n, 1), ...nums);
      }
      
    • Calculator

      function Calculator(
          this: {
              calcFn: (...nums: number[]) => number;
              getResult: Function;
          },
          calcFn: (...nums: number[]) => number,
          ...nums: number[]
      ) {
          this.calcFn = calcFn;
          this.getResult = () => this.calcFn(...nums);
      
          return this;
      }
      
      const _calculator = new (Calculator as any)(calcSumV2, 1, 2, 3, 4, 5);
      _calculator.getResult(); // 15(1+2+3...)
      
      _calculator.calcFn = calcMutiply;
      _calculator.getResult(); // 120(1*2*3...)
      

應用

  • 可以應用在模組替換、服務模組解耦等情境。
  • 其實跟依賴注入很像,都是靠著參數callback的方式,將多個「具體實例」作為參數,傳入其中呼叫使用

REF


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言