1. 程式人生 > 實用技巧 >裝飾器模式(Decorator Pattern)

裝飾器模式(Decorator Pattern)

裝飾器模式(Decorator Pattern)

1.模式動機

一般有兩種方式可以實現給一個類或物件增加行為:

  • 繼承機制,使用繼承機制是給現有類新增功能的一種有效途徑,通過繼承一個現有類可以使得子類在擁有自身方法的同時還擁有父類的方法。但是這種方法是靜態的,使用者不能控制增加行為的方式和時機。
  • 關聯機制,即將一個類的物件嵌入另一個物件中,由另一個物件來決定是否呼叫嵌入物件的行為以便擴充套件自己的行為,我們稱這個嵌入的物件為裝飾器(Decorator)

裝飾模式以對客戶透明的方式動態地給一個物件附加上更多的責任,換言之,客戶端並不會覺得物件在裝飾前和裝飾後有什麼不同。裝飾模式可以在不需要創造更多子類的情況下,將物件的功能加以擴充套件。這就是裝飾模式的模式動機。

2.模式定義

裝飾模式(Decorator Pattern) :動態地給一個物件增加一些額外的職責(Responsibility),就增加物件功能來說,裝飾模式比生成子類實現更為靈活。其別名也可以稱為包裝器(Wrapper),與介面卡模式的別名相同,但它們適用於不同的場合。根據翻譯的不同,裝飾模式也有人稱之為“油漆工模式”,它是一種物件結構型模式。

3.模式結構

裝飾模式包含如下角色:

  • Component: 抽象構件
  • ConcreteComponent: 具體構件
  • Decorator: 抽象裝飾類
  • ConcreteDecorator: 具體裝飾類

4.時序圖

5.程式碼分析

// 元件介面
public interface Component {
	public String getName();
}

// 具體元件實現類A
public class ComponentImplA implements Component {

	private String name;
	
	public ComponentImplA(String name){
		this.name = name;
	}
	
	@Override
	public String getName() {
		// TODO Auto-generated method stub
		return name;
	}

}

// 裝飾器實現元件介面
public abstract class Decorator implements Component {
	// 聚合元件介面
	protected Component component;
	public Decorator(Component component){
		this.component = component;
	}
	
	@Override
	public String getName() {
		return component.getName();
	}

}

// 具體的裝飾器A,這裡新增一個方法appendName;
public class DecoratorImplA extends Decorator {

	public DecoratorImplA(Component component) {
		super(component);
	}
	
	//增加額外的操作
	public void appendName(String name){
		System.out.println(this.component.getName()+name);
	}

}

// 測試
public class DecoratorTest {

	public static void main(String[] args) {
		Component componentImplA = new ComponentImplA("componentA");
		
		DecoratorImplA decoratorImplA = new DecoratorImplA(componentImplA);
		decoratorImplA.appendName("|append1");
		System.out.println(new DecoratorImplB(componentImplA).getName());;
	}
}

6.模式分析

  • 與繼承關係相比,關聯關係的主要優勢在於不會破壞類的封裝性,而且繼承是一種耦合度較大的靜態關係,無法在程式執行時動態擴充套件。在軟體開發階段,關聯關係雖然不會比繼承關係減少編碼量,但是到了軟體維護階段,由於關聯關係使系統具有較好的鬆耦合性,因此使得系統更加容易維護。當然,關聯關係的缺點是比繼承關係要建立更多的物件。
  • 使用裝飾模式來實現擴充套件比繼承更加靈活,它以對客戶透明的方式動態地給一個物件附加更多的責任。裝飾模式可以在不需要創造更多子類的情況下,將物件的功能加以擴充套件。

7.例項

例項:變形金剛

變形金剛在變形之前是一輛汽車,它可以在陸地上移動。當它變成機器人之後除了能夠在陸地上移動之外,還可以說話;如果需要,它還可以變成飛機,除了在陸地上移動還可以在天空中飛翔。

8.裝飾模式的優缺點

優點:
  • 裝飾模式與繼承關係的目的都是要擴充套件物件的功能,但是裝飾模式可以提供比繼承更多的靈活性。
  • 可以通過一種動態的方式來擴充套件一個物件的功能,通過配置檔案可以在執行時選擇不同的裝飾器,從而實現不同的行為。
  • 通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行為的組合。可以使用多個具體裝飾類來裝飾同一物件,得到功能更為強大的物件。
  • 具體構件類與具體裝飾類可以獨立變化,使用者可以根據需要增加新的具體構件類和具體裝飾類,在使用時再對其進行組合,原有程式碼無須改變,符合“開閉原則”
缺點:
  • 使用裝飾模式進行系統設計時將產生很多小物件,這些物件的區別在於它們之間相互連線的方式有所不同,而不是它們的類或者屬性值有所不同,同時還將產生很多具體裝飾類。這些裝飾類和小物件的產生將增加系統的複雜度,加大學習與理解的難度。
  • 這種比繼承更加靈活機動的特性,也同時意味著裝飾模式比繼承更加易於出錯,排錯也很困難,對於多次裝飾的物件,除錯時尋找錯誤可能需要逐級排查,較為煩瑣。
適用環境:
  • 在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。
  • 需要動態地給一個物件增加功能,這些功能也可以動態地被撤銷。
  • 當不能採用繼承的方式對系統進行擴充或者採用繼承不利於系統擴充套件和維護時。不能採用繼承的情況主要有兩類:第一類是系統中存在大量獨立的擴充套件,為支援每一種組合將產生大量的子類,使得子類數目呈爆炸性增長;第二類是因為類定義不能繼承(如final類).

9.模式擴充套件

裝飾模式的簡化-需要注意的問題:

  • 一個裝飾類的介面必須與被裝飾類的介面保持相同,對於客戶端來說無論是裝飾之前的物件還是裝飾之後的物件都可以一致對待。
  • 儘量保持具體構件類Component作為一個“輕”類,也就是說不要把太多的邏輯和狀態放在具體構件類中,可以通過裝飾類

對其進行擴充套件。 - 如果只有一個具體構件類而沒有抽象構件類,那麼抽象裝飾類可以作為具體構件類的直接子類。

10.總結

  • 裝飾模式用於動態地給一個物件增加一些額外的職責,就增加物件功能來說,裝飾模式比生成子類實現更為靈活。它是一種物件結構型模式。
  • 裝飾模式包含四個角色:抽象構件定義了物件的介面,可以給這些對 象動態增加職責(方法);具體構件定義了具體的構件物件,實現了 在抽象構件中宣告的方法,裝飾器可以給它增加額外的職責(方法); 抽象裝飾類是抽象構件類的子類,用於給具體構件增加職責,但是具體職責在其子類中實現;具體裝飾類是抽象裝飾類的子類,負責向構 件新增新的職責。
  • 使用裝飾模式來實現擴充套件比繼承更加靈活,它以對客戶透明的方式動 態地給一個物件附加更多的責任。裝飾模式可以在不需要創造更多子 類的情況下,將物件的功能加以擴充套件。
  • 裝飾模式的主要優點在於可以提供比繼承更多的靈活性,可以通過一種動態的 方式來擴充套件一個物件的功能,並通過使用不同的具體裝飾類以及這些裝飾類的 排列組合,可以創造出很多不同行為的組合,而且具體構件類與具體裝飾類可 以獨立變化,使用者可以根據需要增加新的具體構件類和具體裝飾類;其主要缺 點在於使用裝飾模式進行系統設計時將產生很多小物件,而且裝飾模式比繼承 更加易於出錯,排錯也很困難,對於多次裝飾的物件,除錯時尋找錯誤可能需 要逐級排查,較為煩瑣。
  • 裝飾模式適用情況包括:在不影響其他物件的情況下,以動態、透明的方式給 單個物件新增職責;需要動態地給一個物件增加功能,這些功能也可以動態地 被撤銷;當不能採用繼承的方式對系統進行擴充或者採用繼承不利於系統擴充套件 和維護時。
  • 裝飾模式可分為透明裝飾模式和半透明裝飾模式:在透明裝飾模式中,要求客 戶端完全針對抽象程式設計,裝飾模式的透明性要求客戶端程式不應該宣告具體構 件型別和具體裝飾型別,而應該全部宣告為抽象構件型別;半透明裝飾模式允 許使用者在客戶端宣告具體裝飾者型別的物件,呼叫在具體裝飾者中新增的方法。