1. 程式人生 > >設計模式12:裝飾者模式

設計模式12:裝飾者模式

裝飾者模式

裝飾器模式是一種用於代替繼承的技術,無需通過繼承增加子類就能擴充套件物件的新功能。使用物件的關聯關係代替繼承關係,更加靈活,同時避免型別體系的快速膨脹。

怎麼說?老樣子,先設想個場景

假如我要去樓下買手抓餅吃,一般來說,手抓餅都會提供不同的配料,比如加雞蛋,加雞柳,加肉鬆或者加火腿,不同的配料價錢也不同

是不是感覺餓啦哈哈哈哈,我們繼續...

如果我們要用程式語言來描述的話,我們首先想到的就是先定義一個基類,然後每增加一種搭配就寫個子類

定義一個手抓餅基類,啥都沒加的那種

public class HandPanCake {
	
	String name;
	
	public HandPanCake(String name) {
		this.name = name;
	}
	
	public void eatSense(){
		
		System.out.println("啥都沒加,難吃的一匹");
	}

如果我們要增加配料,比如要加個蛋的話,我們繼承手抓餅類進行拓展

class HandPanCakeWithEgg extends HandPanCake{

	public HandPanCakeWithEgg(String name) {
		super(name);
		
	}
	//增加配料
	public void addEgg(){
		System.out.println("加了雞蛋,美滋滋");
	}
	
	public void eatSense(){
		super.eatSense();
		addEgg();
	}	
}

同樣,我們也可以類似分別寫出增加 雞柳、肉鬆、火腿等的搭配。到目前為止的話,都沒什麼問題

但是,如果某天我撿到錢了,於是買手抓餅的時候同時打算增加雞蛋和火腿這兩樣的組合(就是又加雞蛋又加火腿啦),那麼現在要怎麼操作呢?是不是同樣用繼承的方式呢? 如果我又打算加雞蛋和肉鬆,是不是又要寫個子類呢?再換一種包含多個配料的組合的搭配,則又要再寫個子類,這樣的話,如果我們的搭配非常多,則需要建立非常多的子類,顯然這不是我們想要看到的結果,所以我們就提出了新的思路,而這種思路,就是我們要講的裝飾者模式

再小小的總結下,裝飾者模式解決的是組合搭配搭配問題,當獨立搭配(單獨加雞柳、單獨加雞蛋,單獨加..)數量增加時,其組合搭配數量是爆炸式增長的...

主要角色

  • Component抽象構件角色。真實物件和裝飾物件有相同的介面。這樣,客戶端物件就能夠以與真實物件相同的方式同裝飾物件互動。
  • ConcreteComponent 具體構件角色(真實物件):
  • Decorator裝飾角色:持有一個抽象構件的引用。裝飾物件接受所有客戶端的請求,並把這些請求轉發給真實的物件。這樣,就能在真實物件呼叫前後增加新的功能。
  • ConcreteDecorator具體裝飾角色:負責給構件物件增加新的責任。

例項講解

首先定義一個抽象的手抓餅介面,裡面定義了吃的感覺這一方法

public interface IhandPanCake {
	
	void eatSense();

}

接著是實現手抓餅介面,構建真實物件(是一個啥都沒加的裸餅)

//真實角色,實現抽象介面
public class HandPanCake implements IhandPanCake{
	
	public void eatSense(){
		
		System.out.println("啥都沒加,難吃的一匹");
	}

}

再也就是裝飾者角色,同樣也是實現手抓餅介面,該角色持有一個抽象構建的引用,負責把請求轉發給真實角色

public class HandPanCakeDecorator implements IhandPanCake{
	
	
	protected IhandPanCake handPanCake;
	
	public HandPanCakeDecorator(IhandPanCake handPanCake) {
		super();
		this.handPanCake = handPanCake;
	}

	@Override
	public void eatSense() {
		handPanCake.eatSense();
	}

}

接下來就是定義具體的裝飾角色了,分別負責把雞蛋、火腿這些增加到具體的手抓餅上

加蛋

public class HandPanCakeWithEgg extends HandPanCakeDecorator{

	public HandPanCakeWithEgg(IhandPanCake handPanCake) {
		super(handPanCake);
		
	}
	
	public void addEgg(){
		System.out.println("加了雞蛋,美滋滋");
	}
	
	public void eatSense() {
		super.eatSense();
		addEgg();
	}

}

加雞柳

public class HandPanCakeWithChicken extends HandPanCakeDecorator{

	public HandPanCakeWithChicken(IhandPanCake handPanCake) {
		super(handPanCake);
	}
	
	public void addChicken(){
		System.out.println("加了雞柳,吃了流口水");
	}
	
	public void eatSense() {
		super.eatSense();
		addChicken();
	}

}

加火腿

public class HandPanCakeWithHam extends HandPanCakeDecorator{

	public HandPanCakeWithHam(IhandPanCake handPanCake) {
		super(handPanCake);
	}
	
	public void addHam(){
		System.out.println("加了火腿,有點爽");
	}
	
	public void eatSense() {
		super.eatSense();
		addHam();
	}

}

客戶端測試

採用裝飾者模式的話,當我們要增加多種組合的搭配時,就不需要增加新的子類了,而是

		HandPanCake handPanCake = new HandPanCake();
		handPanCake.eatSense();
		
		System.out.println("老闆,加個雞柳和蛋..................");
		
		HandPanCakeWithChicken handPanCakeWithChickenAndEgg = new HandPanCakeWithChicken(handPanCakeWithEgg);
		handPanCakeWithChickenAndEgg.eatSense();

當然也可以用set方法來組裝裝飾類

優點

  • 擴充套件物件功能,比繼承靈活,不會導致類個數急劇增加
  • 可以對一個物件進行多次裝飾,創造出不同行為的組合,得到功能更加強大的物件具體構建類和具體裝飾類可以獨立變化
  • 使用者可以根據需要自己增加新的具體構件子類和具體裝飾子。

缺點

  • 產生很多小物件。大量小物件佔據記憶體,一定程度上影響效能。
  • 裝飾模式易於出錯,除錯排查比較麻煩。

實際開發中的應用

  • IO中輸入流和輸出流的設計(最典型)
  • Servlet API 中提供了一個request物件的Decorator設計模式的預設實現類HttpServletRequestWrapper,HttpServletRequestWrapper 類,增強了request物件的功能。

橋接模式與裝飾模式

這兩個模式似乎有點像?兩個模式都是為了解決子類過多問題, 但他們的誘因不同:

1.橋接模式物件自身有 沿著多個維度變化的趨勢 , 本身不穩定;

2.裝飾者模式物件自身非常穩定, 只是為了增加新功能/增強原功能。