在講裝飾器模式之前,我先講講代碼實例,在講具體的原理和結構。
情景:遊戲中經常使用槍(英文: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*******************************/
}
在沒有給槍裝任何配件的情況下,這就是裸槍了。開火始終有聲音,射程總是不變。
現在,我們用裝飾器模式給槍裝配件,我們設定如下:
- 如果裝上消音器(英文:Silencer),可以消音
- 如果裝上瞄準鏡(英文: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)
結語:當系統需要新的功能的時候,而且是像舊的類中添加新的代碼。這些新的邏輯通常修飾了原有類的核心設計和職責。這時候可以考慮用裝飾器模式