裝飾者模式
定義:在不改變原有對象的基礎上,將功能附加到對象上,提供了比繼承更有彈性的替代方案,擴展原有對象的功能
類型:結構型
適用場景:
- 擴展一個類的功能或給一個類添加附加的職責
- 動態地給一個對象添加功能
優點:
繼承的有力補充,比繼承靈活,不改變原有對象的情況下給一個對象擴展功能(繼承方式擴展功能,必須都是些可預見的功能,因爲這些功能必須在編譯時就確定,是靜態的,而裝飾者模式是由我們的應用層代碼在運行過程中動態決定加入的方式和時間,同時也提供了一種即插即用的方法,可以在運行期間何時增加何種功能)
通過使用不同的裝飾類以及這些裝飾類的排列組合,可以實現不同的效果
符合開閉原則
缺點:會出現更多的代碼,更多的類,增加程序的複雜度;動態裝飾時,多層裝飾時會更加複雜
UML類圖:
Component:抽象構建角色,以規範準備接收附加責任的對象
ConcreteComponent:具體構件角色,定義一個將要接收復雜責任的類
Decorator:裝飾者角色,持有一個構件(Component)對象,並定義一個與抽象構件接口一致的接口,這個角色不是必須的
ConcreteDecorator:負責給構件對象“貼上”附加的責任
示例
案例1:學生時代,我們每天的三餐基本是在學校的食堂解決,食堂上的每一樣菜都有具體的價格,如一份青菜0.8元、土豆絲1.2元、煎蛋1元、雞腿4.5元,除此之外,我們在排隊點菜前,都會領到一份0.5元的白米飯,點完菜後,飯堂阿姨就會根據你的菜品算出最終的價格。每一天都有成百上千的的學生吃飯,每個人的飯菜喜好都有所不同,算出的價格都有所不同。對於這種不同搭配,我們可以使用裝飾者模式去做,可以很輕鬆地組合各種菜品,計算出一份飯菜的價格
- 這個場景中,我們需要先定義出一個抽象構件角色,這個是所有被裝飾者(具體構件)和裝飾者都需要實現的接口或抽象類
/**
* 抽象構件
*/
public interface Food {
/**
* 食物描述
* @return
*/
String getFoodDesc();
/**
* 食物價格
* @return
*/
double getFoodPrice();
}
- 定義一個抽象構建角色(裝飾者都需要繼承的)
public abstract class FoodDecorator implements Food{
private Food food;//維護了一個被裝飾者的實例對象
public FoodDecorator(Food food){
this.food = food;
}
@Override
public String getFoodDesc() {
return food.getFoodDesc();
}
@Override
public double getFoodPrice() {
return food.getFoodPrice();
}
}
- 定義具體構件角色,也即是被裝飾者類,這裏指的就是白米飯
public class Rice implements Food {
@Override
public String getFoodDesc() {
return "一份白米飯";
}
@Override
public double getFoodPrice() {
return 0.5;
}
}
- 具體的裝飾者類,這裏特指青菜,土豆、雞腿、青菜等一些配菜
public class Egg extends FoodDecorator {
public Egg(Food food) {
super(food);
}
@Override
public double getFoodPrice() {
return 1.0 + super.getFoodPrice();
}
@Override
public String getFoodDesc() {
return super.getFoodDesc() + " 1個煎蛋" ;
}
}
public class Potato extends FoodDecorator{
public Potato(Food food) {
super(food);
}
@Override
public double getFoodPrice() {
return 1.2 + super.getFoodPrice();
}
@Override
public String getFoodDesc() {
return super.getFoodDesc() + " 1份土豆絲" ;
}
}
public class Vegetables extends FoodDecorator {
public Vegetables(Food food) {
super(food);
}
@Override
public double getFoodPrice() {
return 0.8 + super.getFoodPrice();
}
@Override
public String getFoodDesc() {
return super.getFoodDesc() + " 1份青菜" ;
}
}
public class Chicken extends FoodDecorator {
public Chicken(Food food) {
super(food);
}
@Override
public double getFoodPrice() {
return 4.5 + super.getFoodPrice();
}
@Override
public String getFoodDesc() {
return super.getFoodDesc() + " 1個雞腿" ;
}
}
- 客戶端
public class Client {
public static void main(String[] args) {
System.out.println("同學1想吃一份青菜雞腿飯");
Food food = new Rice();
food = new Chicken(food);
food = new Vegetables(food);
System.out.println("食物描述:" + food.getFoodDesc());
System.out.println("計算食物價格:" + food.getFoodPrice());
System.out.println("");
System.out.println("同學2想吃一份煎蛋土豆雞腿飯");
Food food1 = new Rice();
food1 = new Egg(food1);
food1 = new Chicken(food1);
food1 = new Potato(food1);
System.out.println("食物描述:" + food1.getFoodDesc());
System.out.println("計算食物價格:" + food1.getFoodPrice());
System.out.println("");
System.out.println("同學3想吃一份有3個雞腿的飯");
Food food2 = new Chicken(new Chicken(new Chicken(new Rice())));
System.out.println("食物描述:" + food2.getFoodDesc());
System.out.println("計算食物價格:" + food2.getFoodPrice());
}
}
同學1想吃一份青菜雞腿飯
食物描述:一份白米飯 1個雞腿 1份青菜
計算食物價格:5.8同學2想吃一份煎蛋土豆雞腿飯
食物描述:一份白米飯 1個煎蛋 1個雞腿 1份土豆絲
計算食物價格:7.2同學3想吃一份有3個雞腿的飯
食物描述:一份白米飯 1個雞腿 1個雞腿 1個雞腿
計算食物價格:14.0
在這個案例中,我們首先拿到了白米飯這個被裝飾的對象,然後以煎蛋、青菜、雞腿、土豆等各種配菜去裝飾它,並依賴委託對象food(抽象構件角色中以組合方式加入的成員對象)去將價格累加上去。從最終的效果來看,無論以何種方式添加配菜,都可以很容易的算出最終的價格。從客戶端的調用代碼可以看到,各種裝飾對象和被裝飾對象對於客戶端來說都是Food抽象構件的實例,這便是裝飾模式的精髓所在。
- 在上面的例子中,如果被裝飾者只有米飯,那麼抽象構件Component其實是可以忽略的,可以讓抽象裝飾者類直接繼承被裝飾者類,代碼和類圖如下
public abstract class FoodDecorator extends Rice {
private Rice rice;//這裏沒有使用抽象引用,因爲確定只有一個被裝飾對象
public FoodDecorator(Rice rice){
this.rice = rice;
}
@Override
public String getFoodDesc() {
return rice.getFoodDesc();
}
@Override
public double getFoodPrice() {
return rice.getFoodPrice();
}
}
- 我們也可以發現到,其實抽象角色類Decorator並不是必須的,我們完全可以去掉抽象裝飾者角色,把Decorator和ConcreteDecorator的責任合併成一個類,原來的方式是在抽象裝飾者角色類中維護一個被裝飾者對象,現在可以將被裝飾對象放在子類中去維護。下面是我們的裝飾菜品煎蛋類的代碼
public class Egg implements Food{
private Food food; //被裝飾者對象放在了具體的裝飾者類中維護
@Override
public String getFoodDesc() {
return food.getFoodDesc();
}
@Override
public double getFoodPrice() {
return food.getFoodPrice();
}
}
- 最極簡的裝飾者模式,去掉抽象構件接口(Component),去掉抽象裝飾者角色類,(Decorator),但這樣就不再是面向抽象或接口編程了,代碼的靈活性就大大降低,代碼如下
/**
* 被裝飾者沒有實現抽象構件接口
*/
public class Rice {
public String getFoodDesc() {
return "一份白米飯";
}
public double getFoodPrice() {
return 0.5;
}
}
/**
* 裝飾者直接繼承於被裝飾者
*/
public class Egg extends Rice {
private Rice rice;
public Egg(Rice rice){
this.rice = rice;
}
@Override
public String getFoodDesc() {
return "一份煎蛋 " + rice.getFoodDesc();
}
@Override
public double getFoodPrice() {
return 1.0 + rice.getFoodPrice();
}
}
public static void main(String [] args){
Rice rice = new Egg(new Rice());
System.out.println(rice.getFoodDesc());
System.out.println(rice.getFoodPrice());
//一份煎蛋 一份白米飯
//1.5
}
關於裝飾模式的透明性
裝飾模式的本意是在不改變接口的前提下,增強所考慮的類的性能,也就是說,程序不要聲明一個ConcreteComponent類型的變量,而應當聲明一個Component類型的變量。換句話而言,就是被裝飾後的對象,僅僅增強的是抽象構件接口中定義的方法,這種就是比較純粹、透明的裝飾者模式。而實際在增強性能的時候,往往需要建立新的公開方法,這種就是半透明裝飾者模式,比如,我們上面例子中,如果是使用半透明的裝飾者模式,那經過雞腿裝飾者裝飾後的實例,就不僅僅只有抽象構件角色定義的getFoodDesc
和 getFoodPrice
方法,還可能會有獲取雞腿味道getChickenSmell
方法,這是雞腿裝飾類額外的增強方法,若客戶端需要使用到該方法,那麼就要求返回實例是Chicken具體裝飾角色類型,而不是抽象構件角色Food類型
public class Chicken extends FoodDecorator {
public Chicken(Food food) {
super(food);
}
@Override
public double getFoodPrice() {
return 4.5 + super.getFoodPrice();
}
@Override
public String getFoodDesc() {
return super.getFoodDesc() + " 1個雞腿" ;
}
//額外的新方法
public String getChickenSmell(){
return "雞肉味";
}
}
public static void mian(String[] args){
Food food = new Rice();
Chicken chicken = new Chicken(food);
chicken.getChickenSmell();
}
相關的設計模式
- 裝飾者模式與代理模式
裝飾者模式關注在一個對象上動態添加方法,相當於對功能的增強,而代理模式關注於控制對對象的的訪問,代理模式的代理類可以向他的客戶隱藏具體對象的信息,在代碼使用上,代理模式會在代理類中增加一個被代理對象的實例,而在裝飾者模式中,通常會把原始對象當做一個方法參數傳遞給裝飾者的構造器
- 裝飾者模式和適配器模式
裝飾者模式和適配器模式都可以叫做包裝模式,Wraper模式,裝飾者和被裝飾者可以實現相同的接口或者裝飾者是被裝飾者的子類,也就是裝飾者繼承於被裝飾者。在適配器中,適配器和被適配的類,具有不同的接口,當然,也有可能部分接口是重合的。裝飾者模式也可以退化爲半透明裝飾者模式,裝飾者除了提供被裝飾類的接口外,還提供了其他的方法
使用典範
- java中的I/O標準庫
由於Java I/O庫需要很多性能的各種組合,如果這些性能都是用繼承的方法實現的,那麼每一種組合都需要一個類,這樣就會造成大量性能重複的類出現。而如果採用裝飾模式,那麼類的數目就會大大減少,性能的重複也可以減至最少。因此裝飾模式是Java I/O庫的基本模式。
抽象構件(Component)角色:由InputStream扮演。這是一個抽象類,爲各種子類型提供統一的接口。
具體構件(ConcreteComponent)角色:由ByteArrayInputStream、FileInputStream、PipedInputStream、StringBufferInputStream等類扮演。它們實現了抽象構件角色所規定的接口。
抽象裝飾(Decorator)角色:由FilterInputStream扮演。它實現了InputStream所規定的接口。
具體裝飾(ConcreteDecorator)角色:由幾個類扮演,分別是BufferedInputStream、DataInputStream以及兩個不常用到的類LineNumberInputStream、PushbackInputStream。