設計模式學習筆記 - 4

寫在前面

今日特餐:裝飾器模式 (Decorator)

先稍微複習上一篇筆記《設計模式學習筆記 - 3》的內容-觀察者模式:

觀察者模式定義務鍵之間的一對多依賴關係,當一個物件改變狀態時,依賴它的物件都會自動收到通知與更新。

我們以Youtube 訂閱、開啟小鈴鐺為例,認識了觀察者(Observer)與 Subject 之間運作的方式。

今天要學習的設計模式是裝飾器模式,以下正文:

Decorator 裝飾器模式

裝飾器模式(Decorator Pattern) 可以動態的為物件附加額外的職責,使用裝飾器來擴展功能比使用繼承更有彈性。

裝飾器模式

使用書上舉的例子來做介紹:
咖啡廳飲品有各式各樣的咖啡,但每個消費者的需求會有些微變化,例如:

  • A需要一杯濃縮咖啡+兩份鮮奶
  • B需要一杯家常咖啡+燕麥奶

因為咖啡(Component)的單價與材料(Decorator)的單價都個別不同,因此在設計上會參考上圖,讓每杯飲品都能順利的紀錄咖啡與材料的內容與單價。

針對上述例子,我們用程式碼來呈現,看看怎麼實做能夠符合此設計模式的概念:

a. 飲品的部分我們設定了一個超類別- Berverage;
沒有變化的飲品都是實做這個類別,包含是否要增加其他的添加物,以及此產品的價格。

  • 飲品的超類別

    1
    2
    3
    4
    5
    6
    7
    8
    <?php
    Interface Berverage {

    public function getDescription();

    public function getCost();

    }
  • Expresso

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?php
    class Expresso implements Berverage {

    private $description = 'Expresso';

    public function getDescription() {
    return $this->description;
    }

    public function getCost() {
    return 85;
    }

    }
  • House Blend Coffee

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <?php
    class HouseBlend implements Berverage {

    private $description = 'House Blend Coffee';

    public function getDescription() {
    return $this->description;
    }

    public function getCost() {
    return 80;
    }

    }

b. 添加物(調味品)的部分我們以牛奶與燕麥奶為例,可以分別取得加一份牛奶及一份燕麥奶,飲品的總價格會是什麼,並且將內容物描述出來,
提供店員明確的知道客戶點餐的內容:

  • Condiment Decorator 調味品/材料

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?php
    abstract class CondimentDecorator {

    public function __construct(Berverage $berverage) {
    $this->berverage = $berverage;
    }

    abstract public function getDescription();

    abstract public function getCost();

    }
  • Milk

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?php
    class Milk extends CondimentDecorator {

    /** @var Berverage */
    private $berverage;

    public function __construct(Berverage $berverage) {
    $this->berverage = $berverage;
    }

    public function getDescription() {
    return $this->berverage->getDescription() . ', Milk';
    }

    public function getCost() {
    return $this->berverage->getCost() + 20;
    }

    }
  • Oat Milk

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?php
    class OatMilk extends CondimentDecorator {

    /** @var Berverage */
    private $berverage;

    public function __construct(Berverage $berverage) {
    $this->berverage = $berverage;
    }

    public function getDescription() {
    return $this->berverage->getDescription() . ', Oat Milk';
    }

    public function getCost() {
    return $this->berverage->getCost() + 25;
    }

    }
  • 測試:點餐

根據上面的設定,我們可以選擇兩種飲品,以及可添加的材料也有兩種,
今天有一位客人想要點一杯Expresso 加一份牛奶,則我們可以透過程式來實做:

1
2
3
4
5
6
7
<?php

$expresso = new Expresso();
$expresso = new Milk($expresso);
echo 'drink: ' . $expresso->getDescription();
echo 'price: ' . $expresso->getCost();
$order = Order::createOrder(Beverage $expresso);

思考

這邊跳過Order 的實做,也許可以朝著有顧客的姓名、內用還是外帶、桌號等等的方向進行,
就留給大家在練習的時候可以試著建立一張訂單看看。

針對今天練習的部分,未來可以擴充的彈性就比較大,比方說:
未來可以將常見的比例設為產品,拿鐵是Expresso 加 一份牛奶;馥列白可能是加兩份牛奶;摩卡則是加巧克力等等,
雖然會有不同方向的變化,但是都是在我們設定好的基礎飲品+調味品的模式上運作著。


以上是今天的設計模式筆記-裝飾器模式,如有謬誤或是想要一起討論,歡迎來信,謝謝!