iT邦幫忙

2023 iThome 鐵人賽

DAY 2
0
自我挑戰組

深入淺出設計模式 (Head First Design Pattern) - 重點整理及範例分享系列 第 2

[深入淺出設計模式] Ch1 Intro to Design Patterns (2) SimUDuck 鴨子模擬器

  • 分享至 

  • xImage
  •  

各位安安,這篇終於要進入書中的正題啦!以下是作者設定的故事背景⋯⋯

Joe 是一個工程師(Joe是要對決 沒事😂),他任職的公司主要是在做一個鴨子模擬器(SimUDuck)的遊戲,遊戲功能是可以讓不同種類的鴨子在同個池塘裡游泳和呱呱叫。

遊戲初始的設計是以物件導向去寫一個鴨子的類別(class),讓其他種類的鴨子去繼承。公司在去年面臨同行競爭壓力,需要推出新功能來做出市場區隔,所以主管要求Joe要寫出一個fly()的功能讓鴨子飛行。Joe也順著之前的方式,在鴨子的類別中新增一個fly()的方法(method)。隔天馬上接到主管來電罵他,因為有些鴨子是不能飛的,卻因為這個繼承方式讓rubber duck(橡皮鴨)在池塘上空飛來飛去。

這其實也間接代表著,Joe在修改程式碼的時候並沒有考慮到程式後續的維護問題。主管同時也告訴他,之後會陸續增加像是DecoyDuck()(鴨子擺飾)、RedheadDuck()(紅頭鴨)等等,更多種類的鴨子,所以他需要用一個更簡潔的方式來寫出飛行 fly()和發出叫聲 quack()的功能。

❌ 錯誤的方法

既然繼承行不通,Joe轉而想要利用介面(interface)去定義 fly(), quack(),所以程式碼大致上會變成:

<?php
interface Flyable{
    public function fly();            
}

interface Quackable{
    public function quack();            
}

class MallardDuck implements Flyable, Quackable{
    funciton fly(){
        //do something
    }
    funciton quack(){
        //do something
    }
}

class RubberDuck implements Quackable{
    funciton quack(){
        //do something
    }
}
?>

這樣的做法看似有解決問題,其實依然是很難維護的程式碼,因為並不是每種鴨子都使用一樣的叫聲及飛行方式,這也代表,每生成一個鴨子的子類別,就要去修改一次它飛行與叫聲的method內容,效率跟後續維護上會有問題。

⭕️ 正確的方法

我們已經知道,在鴨子這個父類別中,會變動的因素就是鴨子在同一行為上有不同表現。為了不要造成上述問題,所以我們將透過新的Behavior介面去表示鴨子行為,讓不同的類別去實現,

  1. FlyBehavior.php

    interface FlyBehavior{
        public function fly();            
    }
    
    class Flywithwings implements FlyBehavior{
        public function fly(){
            echo "I'm flying!!\r\n";
        }
    }
    
    class FlynoWay implements FlyBehavior{
        public function fly(){
            echo "I cannot fly TT\r\n";
        }
    }
    
    class FlyRocketPowered implements FlyBehavior{
        public function fly(){
            echo "I'm flying like a freaking rocket\r\n";
        }
    }
    
    
  2. QuackBehavior.php

    <?php
    
     interface QuackBehavior{
         public function quack();
     }
    
     class  MuteQuack implements QuackBehavior{
         public function quack(){        
             echo ".......(Silence) \n";
         }
     }
    
     class  Quack implements QuackBehavior{
         public function quack(){        
             echo "QUACK \n";
         }
     }
    
     class  Squeak implements QuackBehavior{
         public function quack(){        
             echo "SQUEAK \n";
         }
     }
     ?>
    
  3. Duck.php

    <?php
    include "FlyBehavior.php";
    
    include "QuackBehavior.php";
    
    
    abstract class Duck{
        public FlyBehavior $flyBehavior;
        public QuackBehavior $quackBehavior;
    
        function __construct(){}
    
        abstract public function display();
        function setFlyBehavior(FlyBehavior $flyAction){
            $this -> flyBehavior = $flyAction;
        }
    
        function setQuackBehavior(QuackBehavior $quackAction){
            $this -> quackBehavior = $quackAction;
        }
    
        function performFly(){
            $this -> flyBehavior -> fly();
        }
    
        function performQuack(){
            $this -> quackBehavior -> quack();
        }
    
        function swim(){
            echo "All duck float!";
        }           
    }
    
    //綠頭鴨
    class MallardDuck extends Duck{    
        function __construct(){
            $this -> quackBehavior = new Quack();
            $this -> flyBehavior = new FlyWithWings();
        }
    
        function display(){
            echo "I'm a real Mallard duck\n";
        }
    }
    
    //橡膠鴨
    class RubberDuck extends Duck{
        function __construct(){
            $this -> quackBehavior = new Squeak();            
        }
    
        function display(){
            echo "I'm a rubber duck";
        }
    }
    
    //裝飾用鴨子
    class DecoyDuck extends Duck{
        function display(){
            echo "I'm a decoy duck";
        }
    }
    ?> 
    
  4. DuckSimulator.php

    <?php
     include "./Class/Duck.php";
     $mallard = new MallardDuck();
     $mallard -> display();
     $mallard -> performFly();
     $mallard -> performQuack();
    
     $rubber = new RubberDuck();
     $rubber -> display();
     $rubber -> performQuack();
    

Output:
https://ithelp.ithome.com.tw/upload/images/20230917/20163178iqxBcEOGOp.png


重點概念:

  1. 在軟體開發上,程式碼極有可能隨著時間拉長去增加跟修改,所以在寫的時候一定要顧慮到它的彈性。
  2. 把程式中「容易變動」跟「保持不變」的程式碼分開,意思就是把「容易變動」的程式碼封裝(encapsulate),這樣需要修改的時就不會動到「保持不變」的部分。
  3. 不要一直想著要透過**實作(implementation)**來達成目的,而是盡量用interface去做。

觀念補充:

  • 封裝 encapsulate:
    物件導向程式設計的原則之一, 將實作和界面分開, 以便讓同界面但不同的實作物件能以一致的面貌讓外界存取
    例如:

        <?php
        class Pamelo{
            public $name;
            public $color;
            function __construct($n, $c){
                $this -> name = $n;
                $this -> color = $c;
                echo $this -> name."<br>";
                echo $this -> color."<br>";
            }
    
            public function BiggerSize(){
                echo "Bigger than apple";
            }
        }
        ?>
    

    多寫一個介面,改成

      <?php
      interface FruitSize{
          public function size();
      }
      class Smaller implements FruitSize{
          public function size(){
              echo "Smaller than apple";
          }
      }
    
      class Bigger implements FruitSize{
          public function size(){
              echo "Bigger than apple";
          }
      }
    
    
      class Pamelo{
          public $name;
          public $color;
          function __construct($n, $c){
              $this -> name = $n;
              $this -> color = $c;
              echo $this -> name."<br>";
              echo $this -> color."<br>";
          }
    
          public function getSize(){
              $bigger = new Bigger();
              $bigger -> size();
          }
      }
      ?>
    

    Output 為:
    https://ithelp.ithome.com.tw/upload/images/20230917/201631785rUzhr1TYh.png

  • 實作 implementation:
    去實際寫出interface或是抽象類別的程式碼內容。例如文中在不同子類別鴨子中都會以不同的Flybehavior實現鴨子飛行的功能,包含Flywithwings(), FlynoWay(), FlyRocketPowered()的內容。


參考資料:

  1. https://programming.im.ncnu.edu.tw/J_Chapter4.htm
  2. 《深入淺出設計模式 (Head First Design Patterns) 》

Disclaimer
因為讀的是原文版,所以難免會有翻譯詞不達意或是專有名詞上的差異,有錯誤的話歡迎在留言區一起交流!


上一篇
[深入淺出設計模式] Ch1 Intro to Design Patterns (1) - 本書閱讀須知
下一篇
[深入淺出設計模式] Ch1 Intro to Design Patterns (3) - 【策略模式 Strategy Pattern】 SimUDuck 鴨子模擬器 &範例補充
系列文
深入淺出設計模式 (Head First Design Pattern) - 重點整理及範例分享35
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言