一、星巴克訂單案例
有下面的需求:
- 咖啡種類/單品咖啡:Espresso(意大利濃咖啡)利朵、ShortBlack律想、LongBlack(美式咖啡)、Decaf(無因咖啡)
- 調料:Milk绍弟、Soy(豆?jié){)技即、Chocolate
- 要求在擴展新的咖啡種類時,具有良好的擴展性晌柬、改動方便姥份、維護方便
- 使用OO來計算不同種類咖啡的費用:客戶可以點單品咖啡,也可以單品咖啡+調料組合
二年碘、咖啡案例的第一個方案
設計一個Drink抽象類,表示飲料展鸡,然后des就是對咖啡的描述屿衅,比如咖啡的名字。cost()方法就是計算費用莹弊,在Drink類中做成一個抽象方法涤久。Decaf就是單品咖啡涡尘,繼承Drink,并實現(xiàn)cost()响迂,Espress&&Milk就是單品咖啡+調料考抄,這個組合很多,詳情看下圖:
這樣設計的話有一個很大的問題就是蔗彤,當我們增加一個單品咖啡川梅,或者一個新的調料,類的數(shù)量就會倍增然遏,就會出現(xiàn)類爆炸贫途。
三、咖啡案例的第二個方案
前面分析到方案1因為咖啡單品+調料組合會造成類的倍增待侵,因此可以做改進丢早,將調料內置到Drink類,這樣就不會造成類數(shù)量過多秧倾。從而提高項目的維護性怨酝,如下圖所示:
但是這個方案也有問題,比如在增加或刪除調料種類時那先,代碼的維護量很大农猬。
對于這個案例我們可以使用裝飾者模式來進行改進。
四胃榕、裝飾者模式
1盛险、裝飾者模式的定義
- 裝飾者模式就是動態(tài)地將新功能附加到對象上。在對象功能擴展方面勋又,它比繼承更有彈性苦掘,裝飾者模式也體現(xiàn)了開閉原則。
2楔壤、裝飾者模式的原理
裝飾者模式就像打包一個快遞
- 主體(被裝飾者)比如:陶瓷鹤啡、衣服
- 包裝(裝飾者)比如:報紙?zhí)畛洹⑺芰吓菽紫⒓埌宓莨濉⒛景?/li>
如下圖所示:
- Component:主體,比如類似咖啡案例中的Drink
- ConcreteComponent和Decorator
ConcreteComponent:具體的主題隙畜,比如前面的各個單品咖啡
Decorator:裝飾者抖部,比如各調料 - 在圖中的Component與ConcreteComponent之間,如果ConcreteComponent類很多议惰,還可以設計一個緩沖層慎颗,將總有的部分提取出來,抽象層是一個類。
五俯萎、用裝飾者模式實現(xiàn)咖啡案例
1傲宜、案例類關系圖
在這個案例之中,類與類之間的關系如下:
- Drink類就是前面說的抽象類(被裝飾者)
- ShortBlack等就是單品咖啡
- Decorator是一個裝飾者夫啊,含有一個被裝飾的對象(Drink drink)
- Decorator的cost方法進行一個費用的疊加計算函卒,遞歸的計算價格
比如點:2份巧克力+1份牛奶的LongBlack,那么關系圖如下:
這圖表明了:
- Milk包含了一個LongBlack
- 1份Chocolate包含了(Milk+LongBlack)
- 再來1份Chocolate包含了(Chocolate+Milk+LongBlack)
這樣的話不管是什么形式的單品咖啡+調料組合撇眯,都能通過遞歸方式方便的組合和維護报嵌。
2、案例代碼實現(xiàn)
(1)定義被裝飾者
先定義一個抽象的被裝飾者Drink類:
package com.cxc.decorator;
public abstract class Drink {
public String des; //描述
private float price = 0.0f;
public String getDes() {
return des;
}
public void setDes(String des) {
this.des = des;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
/**
* 計算費用的抽象方法
* @return
*/
public abstract float cost();
}
coffee類:
package com.cxc.decorator;
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
三個單品咖啡類:
package com.cxc.decorator;
public class Espresso extends Coffee {
public Espresso() {
setDes("意大利咖啡");
setPrice(6.0f);
}
}
package com.cxc.decorator;
public class LongBlack extends Coffee {
public LongBlack() {
setDes(" longblack ");
setPrice(5.0f);
}
}
package com.cxc.decorator;
public class ShortBlack extends Coffee {
public ShortBlack() {
setDes("shortblack");
setPrice(4.0f);
}
}
(2)定義裝飾者
定義裝飾者
package com.cxc.decorator;
public class Decorator extends Drink {
private Drink drink;
public Decorator(Drink drink) { //組合
this.drink = drink;
}
@Override
public float cost() {
//getPrice() : 自己的價格
//drink.cost() : 單品咖啡的價格
return getPrice() + drink.cost();
}
@Override
public String getDes() {
//裝飾者描述+裝飾者價格+被裝飾者描述
return des + " " + getPrice() + " && " + drink.getDes();
}
}
定義具體的裝飾者子類(調料類):
package com.cxc.decorator;
/**
* 具體的裝飾者叛本,在這里是調料
*/
public class Chocolate extends Decorator {
public Chocolate(Drink drink) {
super(drink);
setDes(" 巧克力 ");
setPrice(3.0f); //調味品價格
}
}
package com.cxc.decorator;
public class Soy extends Decorator {
public Soy(Drink drink) {
super(drink);
setDes("豆?jié){");
setPrice(1.5f);
}
}
package com.cxc.decorator;
public class Milk extends Decorator{
public Milk(Drink drink) {
super(drink);
setDes("牛奶");
setPrice(2.0f);
}
}
主方法:
package com.cxc.decorator;
public class CofferBar {
public static void main(String[] args) {
//1.點一份LongBlack
Drink order = new LongBlack();
System.out.println("費用1="+order.cost());
System.out.println("描述="+order.getDes());
//2.加入一份牛奶
order = new Milk(order);
System.out.println("加入一份牛奶費用="+order.cost());
System.out.println("加入一份牛奶描述="+order.getDes());
//3.加一份巧克力
order = new Chocolate(order);
System.out.println("加入一份牛奶再加一份巧克力費用="+order.cost());
System.out.println("加入一份牛奶再加一份巧克力描述="+order.getDes());
//4.又加一份巧克力
order = new Chocolate(order);
System.out.println("加入一份牛奶再加一份巧克力再加一份巧克力費用="+order.cost());
System.out.println("加入一份牛奶再加一份巧克力再加一份巧克力描述="+order.getDes());
}
}
結果如下:
這樣改進后沪蓬,當我們需要再添加一個新的單品咖啡,只需要擴展一個新的單品咖啡類然后去繼承coffee即可来候;添加新的調料也是擴展新的調料類然后去繼承裝飾者即可跷叉。
六、JDK源碼使用到裝飾者模式的場景:IO流
Java的IO結構营搅,F(xiàn)ilterInputStream就是一個裝飾者:
- InputStream是一個抽象類云挟,類似我們前面講的Drink。
- FileInputStream等是InputStream子類转质,類似我們前面的LongBlack园欣。
- FilterInputStream是InputStream子類,類似我們前面的Decorator裝飾者休蟹。
- DataInputStream是FilterInputStream子類沸枯,具體的裝飾者,類似前面的Mile赂弓,Soy等绑榴。
- FilterInputStream類有 InputStream成員變量,即含有被裝飾者盈魁。
由上面的分析翔怎,可以得出其用的就是裝飾者模式。