Decorator pattern
Design Pattern: Decorator Pattern in PHP
Story
從前從前... 有一間早餐店
假設一間早餐店的菜單上面有四樣餐點,菜單是抽象類別,早餐店提供的餐點都繼承它,方法是取得成本。
早餐店老闆想要改變菜單,在主食加上不同的配料。
ex. 漢堡夾蛋、漢堡夾肉、吐司加火腿、吐司加起司..
Problem
如果都繼承菜單抽象類別,會導致類別爆炸。
Intent
想要對一個物件加不同的功能。
Introduction
- 裝飾者模式可以對一個物件更有彈性的加上功能。
- 動態地給一個物件附加一些額外的職責,同時保持相同的介面。
- 就擴充功能來說,裝飾者提供了繼承機制的彈性方案。
UML
角色說明:
- component: 核心角色,抽象元件,是介面或是抽象類別,定義我們最核心的物件。
- concreteComponet: 被裝飾者角色,具體元件,實作抽象元件。
- decorator: 裝飾者角色,實作元件介面或是抽象方法。
- concreteDecorator: 具體裝飾者角色,實作裝飾者。
早餐店UML
How to work
- 拿一個吐司的主食物件
- 以起司物件裝飾
- 以火腿物件裝飾
- 呼叫cost()方法,並依賴委派(delegate)將配料的價格加上去
Example
/**
* 早餐店菜單的抽象類別(component)
*/
abstract class BreakfastComponent
{
protected $name;
// 子類別實作,取得描述
public abstract function getName();
// 子類別實作,取得成本
public abstract function getCost();
}
/**
* 主食吐司具體實作(concreteComponent)
*/
class Toast extends BreakfastComponent
{
public function __construct()
{
$this->name = 'toast';
}
public function getName()
{
return $this->name;
}
public function getCost()
{
return 10;
}
}
/**
* 主食漢堡具體實作(concreteComponent)
*/
class Hamburger extends BreakfastComponent
{
public function __construct()
{
$this->name = 'Hamburger';
}
public function getName()
{
return $this->name;
}
public function getCost()
{
return 20;
}
}
/**
* 配料的抽象類別(decorator)
* 聯繫元件介面的連結
*/
abstract class CondimentDecorator extends BreakfastComponent
{
public function __construct(BreakfastComponent $breakfast)
{
$this->name = $breakfast;
}
}
/**
* 配料Cheese的具體實作(concreteComponent)
*/
class Cheese extends CondimentDecorator
{
public function getName()
{
return $this->name->getName() . ' add Cheese';
}
public function getCost()
{
return 10 + $this->name->getCost();
}
}
/**
* 配料Ham的具體實作(concreteComponent)
*/
class Ham extends CondimentDecorator
{
public function getName()
{
return $this->name->getName() . ' add Ham';
}
public function getCost()
{
return 15 + $this->name->getCost();
}
}
Client 如下:
/**
* Client
*/
// 點吐司
$toast = new Toast();
var_dump('餐點:' . $toast->getName());
var_dump('價格:' . $toast->getCost() . '元');
// 點吐司加起司
$toastAddCheese = new Cheese($toast);
var_dump('餐點:' . $toastAddCheese->getName());
var_dump('價格:' . $toastAddCheese->getCost() . '元');
// 點吐司加火腿
$toastAddHam = new Ham($toast);
var_dump('餐點:' . $toastAddHam->getName());
var_dump('價格:' . $toastAddHam->getCost() . '元');
// 點吐司加起司加火腿
$toastAddCheese = new Cheese($toast);
$toastAddCheeseAddHam = new Ham($toastAddCheese);
var_dump('餐點:' . $toastAddCheeseAddHam->getName());
var_dump('價格:' . $toastAddCheeseAddHam->getCost() . '元');
Advantages and Disadvantages
Advantages: 彈性動態擴充功能,不影響原本的程式。
Disadvantages: 裝飾者會導致程式中出現很多小類別,導致程式變複雜。
When
想在既有的物件上添加一些功能,而不希望影響其他物件。
藉由Decorator模式,你可以先建立核心功能,之後在裝飾這些核心,為客戶加上特殊功能。
Discussion
公司產品適用地方?
Reference
設計模式(九)裝飾者模式(Decorator Pattern)
<書籍>深入淺出:設計模式
<書籍>PHP設計模式
<書籍>設計模式之禪