重点 — 将子类替换掉,也不影响原程式运作

  • 继承原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

  • https://en.wikipedia.org/wiki/SOLID
  • https://www.pythontutorial.net/python-oop/python-liskov-substitution-principle/
  • https://dev.to/cleancodestudio/liskovs-substitution-principle-python-design-patterns-31c2
  • https://zh.wikipedia.org/wiki/里氏替换原则