從喫雞中論裝飾器模式

在講裝飾器模式之前,我先講講代碼實例,在講具體的原理和結構。

情景:遊戲中經常使用槍(英文:Gun),有手槍(英文:Pistol),狙擊槍(英文:SniperRifle)等等。
然後槍有個基本功能,肯定是fire,也許shot更恰當,如果用代碼實現,大致如下:

  • 抽象類:槍(Gun)
public abstract class Gun {
	//開火
	public abstract Fire fire();
}
  • 具體類:手槍(Pistol),繼承於 Gun
public class Pistol extends Gun{

	@Override
	public Fire fire() {
		Fire fire = new Fire();
		fire.setRange(50); //射程50
		fire.setVoice(false); //開火有聲音
		return fire;
	}
}
  • 具體類:狙擊槍(SniperRifle),繼承於 Gun
public class SniperRifle extends Gun{

	@Override
	public Fire fire() {
		Fire fire = new Fire();
		fire.setRange(400);//射程400
		fire.setVoice(false);//開火有聲音
		return fire;
	}
}

槍可能有很多種,這裏,就是舉兩個。

  • 開火屬性類,表示開火的屬性。
public class Fire {
	//射程
	private int range; 
	//開槍是否有聲音
	private boolean voice; 
	
	@Override
	public String toString() {
		return "[射程:" + range + ","+((voice) ? "消音":"沒有消音")+"]";
	}

	/***************************getter & setter*******************************/
	public int getRange() {return range;}
	public void setRange(int range) {this.range = range;}

	public boolean isVoice() {return voice;}
	public void setVoice(boolean voice) {this.voice = voice;}
	/***************************getter & setter*******************************/
}

在沒有給槍裝任何配件的情況下,這就是裸槍了。開火始終有聲音,射程總是不變。
現在,我們用裝飾器模式給槍裝配件,我們設定如下:

  1. 如果裝上消音器(英文:Silencer),可以消音
  2. 如果裝上瞄準鏡(英文:Collimation Mirror),可以增加射程。

我們代碼實現如 :

  • 我們定義一個配件的父類,這個類可以不是抽象類,但是很明顯,定義爲abstract 更符合語義
/**
 * 配件父類
 */
public abstract class DecorateParts extends Gun{

	protected Gun gun;

	/**核心方法,這個方法很關鍵,是裝飾器模式的核心 */
	@Override
	public Fire fire() {
		return (gun != null) ? gun.fire() : null;
	}

	/**安裝配件*/
	public void install(Gun gun) {
		this.gun = gun;
	}
	
}
  • 具體配件類:消音器
/**
 * 消音器
 */
public class Silencer extends DecorateParts{
	
	@Override
	public Fire fire() {
		Fire fireResult = super.fire();
		fireResult.setVoice(true);//裝上消音
		return fireResult;
	}
	
}
  • 具體配件類:瞄準鏡
/**
 * 瞄準鏡
 */
public class CollimationMirror extends DecorateParts{
	//增加的射程,這裏設定增加100射程
	private int addRange = 100;
	
	@Override
	public Fire fire() {
		Fire fireResult = super.fire();
		//增加射程
		fireResult.setRange(fireResult.getRange() + addRange);
		return fireResult;
	}
	
}

至此,配件相關的類也寫完了,接下來,測試一下:

public class DecorateMain {
	
	public static void main(String[] args) {
		//創建一把手槍
		Pistol gun = new Pistol();
		System.out.println("手槍-[裸槍]-" + gun.fire());
		
		//創建一個瞄準鏡,裸槍上,安裝瞄準鏡
		CollimationMirror mirrorGun = new CollimationMirror();
		mirrorGun.install(gun);
		System.out.println("手槍-[瞄準鏡]-" + mirrorGun.fire());
		
		//創建一個消音器,在安裝了瞄準鏡的基礎上,再安裝消音器
		Silencer silencerMirrorGun = new Silencer();
		silencerMirrorGun.install(mirrorGun);
		System.out.println("手槍-[瞄準鏡,消音器]-" + silencerMirrorGun.fire());
		
		//創建一個消音器,裸槍上,安裝消音器
		Silencer silencerGun = new Silencer();
		silencerGun.install(gun);
		System.out.println("手槍-[消音器]-" + silencerGun.fire());
	}
}

具體的運行結果如下:
這裏寫圖片描述

在不影響槍的情況下,對槍進行裝飾,主要還是看配件的3個類。
DecorateParts 本質上他不是配件,他繼承了Gun,然後還有一個Gun的引用(protected Gun gun),
可以說這是裝飾器模式關鍵。
我們從代碼中可以知道,手槍,狙擊槍是在Gun的基礎上進行擴展,而消音器,瞄準器是在DecorateParts 上擴展。

/**
 * 消音器
 */
public class Silencer extends DecorateParts{
	
	@Override
	public Fire fire() {
		Fire fireResult = super.fire();
		fireResult.setVoice(true);//裝上消音
		return fireResult;
	}
	
}

我們主要分析下消音器,使用裝飾器模式,基本上第一個的方法的就是super.fire(),也就是調用父類的同名方法,
這也代理模式有些相似,但是並不相同。看如下代碼,也是就是配件的裝配過程:
Pistol gun = new Pistol();
mirrorGun.install(gun);
silencerMirrorGun.install(mirrorGun);

對於silencerMirrorGun;super.fire()其實就是mirrorGun.fire();
對於mirrorGun,super.fire()其實是gun.fire()
由此可見:silencerMirrorGun.fire()中第一步調用了mirrorGun.fire(),mirrorGun.fire()第一步調用了gun.fire()。
所以mirrorGun.fire()是在gun.fire()上裝飾,而silencerMirrorGun.fire()在mirrorGun.fire()的基礎上裝飾。
這就是裝飾模式的真正意義。而代理模式也是類似,不過代理一般不會改變方法的返回結果,裝飾器模式一遍都是多層的,也就是說裝配了很多東西,而代理模式一般也就只有一層而已。

最後來看看裝飾器模式的類圖結構,圖片來自大話設計模式,如下:
這裏寫圖片描述

與我上面的例子其實很容易對應,如下:
(Component – Gun)
(ConcreteComponent – Pistol)
(Decorator – DecorateParts)
(ConreteDecoratorA – Silencer)
(ConreteDecoratorB – CollimationMirror)

結語:當系統需要新的功能的時候,而且是像舊的類中添加新的代碼。這些新的邏輯通常修飾了原有類的核心設計和職責。這時候可以考慮用裝飾器模式

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章