- 幾分鐘帶你搞懂策略模式
- 幾分鐘帶你搞懂觀察者模式
- 一文徹底搞明白工廠和抽象工廠
- 一文搞明白裝飾者模式
- 最全單例模式
關於裝飾者模式的定義,我就直接引用Head First了:裝飾者模式動態的將責任附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案 。 其實裝飾者模式的重點在於給對象動態的附加職責,通過對象組合的方式,運行時裝飾對象,在不改變任何底層代碼的情況下,給現有對象賦予新的職責。
裝飾者模式
現在我們要爲賣煎餅的大媽設計一套系統,讓大媽能更好的算賬收錢。大媽主要經營煎餅(7元),並可以往其中添加配料烤腸(1元),雞蛋(1元),如果生意紅火,可能會考慮擴大經營其他小吃或添加配料種類。現需要設計出一套系統,以便快速計算出每位顧客所購小吃的價格。
這是一個食品類接口。同樣,要符合針對接口編程,不針對實現編程的OO原則。
public interface ISnack {
// 食物的描述
String getDescription();
// 食物的價格
double cost();
}
這是一個配料類接口。配料類接口IDecoratorSnack繼承自ISnack接口,因爲需要兩接口的實現類(裝飾者與被裝飾對象)具有相同的超類型,只有這樣IDecoratorSnack接口才能取代ISnack接口。
/**
* 這是配料類的接口。
*
* <p>裝飾者與被裝飾者需要具有相同的接口,
* 這樣用戶使用裝飾者對象纔會和使用原始對象一樣。
*/
public interface IDecoratorSnack extends ISnack {
// 可根據需要擴展屬性,如配料大份,小份等
}
這是具體的食品-煎餅類。
public class PancakeSnack implements ISnack {
@Override
public String getDescription() {
return "煎餅";
}
@Override
public double cost() {
return Price.PancakeSnack.price;
}
}
食品類(被裝飾者)既可以單獨使用,也可以被配料類(裝飾者)包着使用,因爲裝飾者和被裝飾者對象具有相同的超類型,所以在任何需要原始對象(被包裝的)的場合,都可以用裝飾過的對象替代它。
如果,我想要一份加雞蛋和烤腸的煎餅價格是多少?單價類見下:
public enum Price {
//雞蛋、烤腸、煎餅
Egg(1.0), Sausage(1.0), PancakeSnack(7.0);
double price;
private Price(double price) {
this.price = price;
}
}
這是我們的具體配料雞蛋類。
public class Egg implements IDecoratorSnack {
// 持有被修飾的對象
ISnack iSnack;
public Egg(ISnack iSnack) {
this.iSnack = iSnack;
}
@Override
public String getDescription() {
return iSnack.getDescription() + ",加雞蛋";
}
@Override
public double cost() {
// 被修飾對象的價格 + 雞蛋價格
return iSnack.cost() + Price.Egg.price;
}
}
這是具體的烤腸配料類。
public class Sausage implements IDecoratorSnack{
//持有被修飾對象的引用
ISnack iSnack;
public Sausage(ISnack iSnack) {
this.iSnack = iSnack;
}
@Override
public String getDescription() {
return iSnack.getDescription() + ",加香腸";
}
@Override
public double cost() {
// 被修飾對象的價格 + 烤腸價格
return iSnack.cost() + Price.Sausage.price;
}
}
每個裝飾者都持有一個被裝飾者的對象,這個被裝飾者對象不一定是原始對象,也可能是被包裝了多層的對象。通過這種組合,加入了新的行爲。符合對擴展開放,對修改關閉的OO原則。
我們來收錢吧。我們能夠發現,我們能夠使用被裝飾的對象就像使用原始對象一樣,這歸功於裝飾者與被裝飾者具備同樣的接口。同樣一個原始對象是能夠被包裝多層的,而在使用者的眼裏,它只是一個被賦予了新功能的原始對象而已。
public class DecoratorPatternTest {
public static void main(String[] ags) {
// 煎餅 + 雞蛋 + 烤腸
ISnack snackOneISnack = new PancakeSnack();
snackOneISnack = new Egg(snackOneISnack);
snackOneISnack = new Sausage(snackOneISnack);
// 這樣就能夠打印出,我們的(煎餅 + 雞蛋 + 烤腸)價格和描述了
System.out.println(snackOneISnack.getDescription() + "價格:" + snackOneISnack.cost() + "元");
}
}
而且擴展起來方便,我們隨時加入不同種類小吃和配料類。如我們現在要加一種烤冷麪類。
public class ColdRoastSnake implements ISnack{
@Override
public String getDescription() {
return "烤冷麪";
}
@Override
public double cost() {
return Price.ColdRoastSnake.price;
}
}
我們再添加一個雞裏脊肉類。
public class Chicken implements IDecoratorSnack{
ISnack iSnack;
public Chicken(ISnack iSnack) {
this.iSnack = iSnack;
}
@Override
public String getDescription() {
return iSnack.getDescription() + ",加裏脊肉";
}
@Override
public double cost() {
return iSnack.cost() + Price.Chicken.price;
}
}
好了,如果用戶購買烤冷麪+雞蛋+裏脊肉,價格該多少呢?
public class DecoratorPatternTest {
public static void main(String[] ags) {
// 烤冷麪 + 雞蛋 + 裏脊肉
ISnack snackOneISnack = new ColdRoastSnake();
snackOneISnack = new Egg(snackOneISnack);
snackOneISnack = new Chicken(snackOneISnack);
// 這樣就能夠打印出,我們的(烤冷麪 + 雞蛋 + 裏脊肉)價格和描述了
System.out.println(snackOneISnack.getDescription() + "價格:" + snackOneISnack.cost() + "元");
}
}
需要注意的是,通過利用裝飾者模式,會造成設計中產生大量的小類,如果過度使用,會使程序變得很複雜。另外可能還會出現類型問題,如果把代碼寫成依賴於具體的被裝飾者類型,不針對抽象接口進行編程,那麼就會出現問題。