Files
Obsidian-Main/02. PARA/03. Resources(資源)/Design Pattern.md
Awin Huang 3810da15fe vault backup: 2022-06-14 21:15:16
Affected files:
.obsidian/plugins/oz-image-plugin/data.json
.obsidian/workspace
02. PARA/03. Resources(資源)/Design Pattern.md
2022-06-14 21:15:16 +08:00

5.2 KiB
Raw Blame History

tags, aliases, date, time, description
tags aliases date time description
2022-05-26 15:12:04

策略模式Strategy

策略模式可以提供不一樣的演算法,但是又不用更改程式。 以常見的鴨子為例有一個基礎類別Duck如何衍生出會飛得鴨子跟不會飛的鴨子抑或是會叫的跟不會叫的 第一部是將會變動的部份分離出來,讓鴨子類別不需要去在乎飛跟叫的問題。 再來是把飛跟叫的部份包裝成另一個class並以之為基礎類別來實做出「實際的類別」。 ^e59e9f

以一般C++的override方法會用的方式大致是這樣

  • !!!col
    • 1

      基礎類別

      class duck {
          duck() {}
          void fly() {}
          void makeSound() {}
          void run() {}
      }
      
    • 1

      衍生類別,遙控鴨子,會飛不會叫

      class duckRC : public duck {
          duckRC() {}
          void fly() { printf("I can fly"); }
          void makeSound() { printf("I cannot make sound!"); }
      }
      
    • 1

      衍生類別,木頭鴨子,不會飛不會叫

      class duckWood : public duck {
          duckWood() {}
          void fly() { printf("I cannot fly"); }
          void makeSound() { printf("I cannot make sound!"); }
      }
      

但是這樣的話如果基礎類別會更改的話,那麼子類別也全部都會受影響,例如,現在希望所有的鴨子都要有一個「跑」的功能,所以我們就在基礎類別裡加一個run()

class duck {
    void fly() {}
    void makeSound() {}
    void run() { printf("I can run"); }
}

結果現在木頭鴨子也能跑了,這不符合我們的設計,所以我們必須回頭更改木頭鴨子的程式,改成

class duckWood : public duck {
    void fly() { printf("I cannot fly"); }
    void makeSound() { printf("I cannot make sound!"); }
    void run() { printf("I cannot run!"); }  // <- 要改!
}

如果我們類別很多那麼就很可能有沒改到的漏網之魚bug就發生了。

封裝變動的部份

比較好的方法一旦類別寫完之後,我們就不要動它了,日後新增的功能也不可以影響到他。我們把「會變動」的部份分離出來,變成各自的類別。 為了簡化我們討論「飛」這個功能就好「飛」只分成「會飛」跟「不會飛」2種類別

  • !!!col
    • 1

      基礎類別IFly

      class IFly {
          void doFly() = 0;
      }
      
    • 1

      衍生類別CanFly

      class CanFly {
          void doFly() { printf("I can fly"); }
      }
      
    • 1

      衍生類別CannotFly

      class CannotFly {
          void doFly() { printf("I cannot fly"); }
      }
      

回到鴨子的基礎類別這邊,基礎類別duck本來是直接擁有fly()這個member function現在我們把他改成他擁有的是IFly

class duck {
    duck() {}
    fly() { fly->doFly() };
    IFly* fly = nullptr;
    ...
}

重寫遙控鴨子這個衍生類別:

class duckRC : public duck {
    duckRC() {
        this->fly = new CanFly();
    }
    ...
}

重寫木頭鴨子,不會飛,所以:

class duckWood : public duck {
    duckWood() {
        this->fly = new CannotFly();
    }
}

現在,不管是遙控鴨子或是木頭鴨子,在被呼叫fly()的時候都是根據它fly的「實際類別」來動作遙控鴨子的fly()呼叫的是CanFlydoFly(),木頭鴨子的fly()呼叫的是CannotFlydoFly()它們的動作完全取決於他們初始化的方式而他們初始化的方式則取決於你的產品定義要是今天你需要不同的飛行方式增加新的fly類別即可不會影響到舊有的。要是鴨子基礎類別增加了新的行為如run那麼就幫鴨子基礎類別的run()加個什麼都不做的預設動作就好,反正舊有的鴨子衍生類別本來就對這個新行為沒反應。要是CanFly的定義改變了,那麼你就是改變了使用CanFly的所有類別,這是你的定義明確的改變了,不是有程式被「額外」的改變了。

  • !!!col
    • 1

      原本直接繼承的方式

      !Pasted image 20220526182952.png
    • 2

      封裝變動的部份

      !Pasted image 20220526183019.png

這樣做的另一個好處是fly的初始化是動態的只要再多一個set() function就可以動態的切換實作也就是說你可以從設定檔來決定你的鴨子要長什麼樣子。

觀察者模式Observer Pattern

有一個會產生變動的主角subject與一堆需要觀察變動的觀察者Observer。觀察者向主角註冊當主角發生變化的時候發後通知給觀察者。 !20220614154819_Observer_Pattern.png

Subject方面attach() 就是註冊觀察者,也可以叫做 register()、add() 之類。 detach() 用來移除用戶,也可以叫做 unregister()、remove() 之類。 notify()則是當發生變化時,用來通知所有觀察者的實作。

觀察者方面必須實作 update() 才能收到通知。