裝飾模式 (Decorator Pattern)

裝飾模式可以實現動態的爲對象添加功能,是從一個對象外部來給對象添加功能。一般有兩種方式能夠實現給一個類或對象增長行爲:html

  • 繼承機制,使用繼承機制是給現有類添加功能的一種有效途徑,經過繼承一個現有類可使得子類在擁有自身方法的同時還擁有父類的方法。可是這種方法是靜態的,用戶不能控制增長行爲的方式和時機。
  • 組合機制,即將一個類的對象嵌入另外一個對象中,由另外一個對象來決定是否調用嵌入對象的行爲以便擴展本身的行爲,咱們稱這個嵌入的對象爲裝飾器(Decorator)

顯然,爲了擴展對象功能頻繁修改父類或者派生子類這種方式並不可取。在面向對象的設計中,咱們應該儘可能使用對象組合,而不是對象繼承來擴展和複用功能。裝飾器模式就是基於對象組合的方式,能夠很靈活的給對象添加所須要的功能。裝飾器模式的本質就是動態組合。動態是手段,組合纔是目的。總之,裝飾模式是經過把複雜的功能簡單化,分散化,而後在運行期間,根據須要來動態組合的這樣一個模式。git

裝飾模式定義

裝飾模式(Decorator Pattern) :動態地給一個對象增長一些額外的職責(Responsibility),就增長對象功能來講,裝飾模式比生成子類實現更爲靈活。其別名也能夠稱爲包裝器(Wrapper),與適配器模式的別名相同,但它們適用於不一樣的場合。根據翻譯的不一樣,裝飾模式也有人稱之爲「油漆工模式」,它是一種對象結構型模式。github

裝飾模式的優勢

  • 裝飾模式與繼承關係的目的都是要擴展對象的功能,可是裝飾模式能夠提供比繼承更多的靈活性。
  • 能夠經過一種動態的方式來擴展一個對象的功能,經過配置文件能夠在運行時選擇不一樣的裝飾器,從而實現不一樣的行爲。
  • 經過使用不一樣的具體裝飾類以及這些裝飾類的排列組合,能夠創造出不少不一樣行爲的組合。可使用多個具體裝飾類來裝飾同一對象,獲得功能更爲強大的對象。

模式結構和說明

裝飾器模式UML

  • 聚合關係用一條帶空心菱形箭頭的直線表示,上圖表示Component聚合到Decorator上,或者說Decorator由Component組成。
  • 繼承關係用一條帶空心箭頭的直接表示
  • 看懂UML類圖請看這個文檔

Component:組件對象的接口,能夠給這些對象動態的添加職責;bash

ConcreteComponent:具體的組件對象,實現了組件接口。該對象一般就是被裝飾器裝飾的原始對象,能夠給這個對象添加職責;app

Decorator:全部裝飾器的父類,須要定義一個與Component接口一致的接口(主要是爲了實現裝飾器功能的複用,即具體的裝飾器A能夠裝飾另一個具體的裝飾器B,由於裝飾器類也是一個Component),並持有一個Component對象,該對象其實就是被裝飾的對象。若是不繼承Component接口類,則只能爲某個組件添加單一的功能,即裝飾器對象不能再裝飾其餘的裝飾器對象。學習

ConcreteDecorator:具體的裝飾器類,實現具體要向被裝飾對象添加的功能。用來裝飾具體的組件對象或者另一個具體的裝飾器對象。ui

裝飾器的示例代碼

1.Component抽象類, 能夠給這些對象動態的添加職責this

abstract class Component
{
	abstract public function operation();
}
複製代碼

2.Component的實現類spa

class ConcreteComponent extends Component
{
	public function operation()
	{
		echo __CLASS__ .  '|' . __METHOD__ . "\r\n";
	}
}
複製代碼

3.裝飾器的抽象類,維持一個指向組件對象的接口對象, 並定義一個與組件接口一致的接口翻譯

abstract class Decorator extends Component
{
	/**
	 * 持有Component的對象
	 */
	protected $component;

	/**
	 * 構造方法傳入
	 */
	public function __construct(Component $component)
	{
		$this->component = $component;
	}

	abstract public function operation();
}
複製代碼

4.裝飾器的具體實現類,向組件對象添加職責,beforeOperation(),afterOperation()爲先後添加的職責。

class ConcreteDecoratorA extends Decorator
{
	//在調用父類的operation方法的前置操做
	public function beforeOperation()
	{
		echo __CLASS__ . '|' . __METHOD__ . "\r\n";
	}

	//在調用父類的operation方法的後置操做
	public function afterOperation()
	{
		echo __CLASS__ . '|' . __METHOD__ . "\r\n";
	}

	public function operation()
	{
		$this->beforeOperation();
		$this->component->operation();//這裏能夠選擇性的調用父類的方法,若是不調用則至關於徹底改寫了方法,實現了新的功能
		$this->afterOperation();
	}
}

class ConcreteDecoratorB extends Decorator
{
	//在調用父類的operation方法的前置操做
	public function beforeOperation()
	{
		echo __CLASS__ . '|' . __METHOD__ . "\r\n";
	}

	//在調用父類的operation方法的後置操做
	public function afterOperation()
	{
		echo __CLASS__ . '|' . __METHOD__ . "\r\n";
	}

	public function operation()
	{
		$this->beforeOperation();
		$this->component->operation();//這裏能夠選擇性的調用父類的方法,若是不調用則至關於徹底改寫了方法,實現了新的功能
		$this->afterOperation();
	}
}
複製代碼

5.客戶端使用裝飾器

class Client
{
	public function main()
	{
		$component = new ConcreteComponent();
		$decoratorA = new ConcreteDecoratorA($component);
		$decoratorB = new ConcreteDecoratorB($decoratorA);
		$decoratorB->operation();
	}
}

$client = new Client();
$client->main();
複製代碼

6.運行結果

oncreteDecoratorB|ConcreteDecoratorB::beforeOperation
ConcreteDecoratorA|ConcreteDecoratorA::beforeOperation
ConcreteComponent|ConcreteComponent::operation
ConcreteDecoratorA|ConcreteDecoratorA::afterOperation
ConcreteDecoratorB|ConcreteDecoratorB::afterOperation
複製代碼

裝飾模式須要注意的問題

  • 一個裝飾類的接口必須與被裝飾類的接口保持相同,對於客戶端來講不管是裝飾以前的對象仍是裝飾以後的對象均可以一致對待。
  • 儘可能保持具體組件類ConcreteComponent的輕量,不要把主邏輯以外的輔助邏輯和狀態放在具體組件類中,能夠經過裝飾類對其進行擴展。 若是隻有一個具體組件類而沒有抽象組件類,那麼抽象裝飾類能夠做爲具體組件類的直接子類。

適用環境

  • 須要在不影響組件對象的狀況下,以動態、透明的方式給對象添加職責。
  • 當不能採用繼承的方式對系統進行擴充或者採用繼承不利於系統擴展和維護時能夠考慮使用裝飾類。

本文已經收錄在系列文章Laravel源碼學習裏,歡迎訪問閱讀。

相關文章
相關標籤/搜索