1. 程式人生 > 其它 >設計模式—裝飾者設計模式

設計模式—裝飾者設計模式

1.概述
舉個例子

快餐店有炒麵、炒飯這些快餐,可以額外附加雞蛋、火腿、培根這些配菜,當然加配菜需要額外加錢,每個配菜的價錢通常不太一樣,那麼計算總價就會顯得比較麻煩。

上圖是使用繼承的類圖,使用繼承的方式存在的問題:

擴充套件性不好
如果要再加一種配料(火腿腸),我們就會發現需要給FriedRice和FriedNoodles分別定義一個子類。如果要新增一個快餐品類(炒河粉)的話,就需要定義更多的子類。
產生過多的子類
裝飾者模式定義

動態的將新功能附加到物件上。在物件功能擴充套件方面,它比繼承更有彈性。指在不改變現有物件結構的情況下,動態地給該物件增加一些職責(即增加其額外功能)的模式。

2.結構
裝飾(Decorator)模式中的角色:

抽象構件(Component)角色 :定義一個抽象介面以規範準備接收附加責任的物件。
具體構件(Concrete Component)角色 :實現抽象構件,通過裝飾角色為其新增一些職責。
抽象裝飾(Decorator)角色 : 繼承或實現抽象構件,幷包含具體構件的例項,可以通過其子類擴充套件具體構件的功能。
具體裝飾(ConcreteDecorator)角色 :實現抽象裝飾的相關方法,並給具體構件物件新增附加的責任。
3.案例
使用裝飾者模式對快餐店案例進行改進

類圖如下


程式碼如下

//快餐介面
public abstract class FastFood {
private float price;
private String desc;

public FastFood() {
}

public FastFood(float price, String desc) {
this.price = price;
this.desc = desc;
}

public void setPrice(float price) {
this.price = price;
}

public float getPrice() {
return price;
}

public String getDesc() {
return desc;
}

public void setDesc(String desc) {
this.desc = desc;
}

public abstract float cost(); //獲取價格
}

//炒飯
public class FriedRice extends FastFood {

public FriedRice() {
super(10, "炒飯");
}

public float cost() {
return getPrice();
}
}

//炒麵
public class FriedNoodles extends FastFood {

public FriedNoodles() {
super(12, "炒麵");
}

public float cost() {
return getPrice();
}
}

//配料類
public abstract class Garnish extends FastFood {

private FastFood fastFood;

public FastFood getFastFood() {
return fastFood;
}

public void setFastFood(FastFood fastFood) {
this.fastFood = fastFood;
}

public Garnish(FastFood fastFood, float price, String desc) {
super(price,desc);
this.fastFood = fastFood;
}
}

//雞蛋配料
public class Egg extends Garnish {

public Egg(FastFood fastFood) {
super(fastFood,1,"雞蛋");
}

public float cost() {
return getPrice() + getFastFood().getPrice();
}

@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}

//培根配料
public class Bacon extends Garnish {

public Bacon(FastFood fastFood) {

super(fastFood,2,"培根");
}

@Override
public float cost() {
return getPrice() + getFastFood().getPrice();
}

@Override
public String getDesc() {
return super.getDesc() + getFastFood().getDesc();
}
}

//測試類
public class Client {
public static void main(String[] args) {
//點一份炒飯
FastFood food = new FriedRice();
//花費的價格
System.out.println(food.getDesc() + " " + food.cost() + "元");

System.out.println("========");
//點一份加雞蛋的炒飯
FastFood food1 = new FriedRice();

food1 = new Egg(food1);
//花費的價格
System.out.println(food1.getDesc() + " " + food1.cost() + "元");

System.out.println("========");
//點一份加培根的炒麵
FastFood food2 = new FriedNoodles();
food2 = new Bacon(food2);
//花費的價格
System.out.println(food2.getDesc() + " " + food2.cost() + "元");
}
}

好處

飾者模式可以帶來比繼承更加靈活性的擴充套件功能,使用更加方便,可以通過組合不同的裝飾者物件來獲取具有不同行為狀態的多樣化的結果。裝飾者模式比繼承更具良好的擴充套件性,完美的遵循開閉原則,繼承是靜態的附加責任,裝飾者則是動態的附加責任。
裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴充套件一個實現類的功能。
4.使用場景
當不能採用繼承的方式對系統進行擴充或者採用繼承不利於系統擴充套件和維護時。
不能採用繼承的情況主要有兩類:
第一類 是系統中存在大量獨立的擴充套件,為支援每一種組合將產生大量的子類,使得子類數目呈爆炸性增長;
第二類 是因為類定義不能繼承(如final類)
在不影響其他物件的情況下,以動態、透明的方式給單個物件新增職責。
當物件的功能要求可以動態地新增,也可以再動態地撤銷時。
5.JDK原始碼解析
IO流中的包裝類使用到了裝飾者模式。BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter。
我們以BufferedWriter舉例來說明,先看看如何使用BufferedWriter

public class Demo {
public static void main(String[] args) throws Exception{
//建立BufferedWriter物件
//建立FileWriter物件
FileWriter fw = new FileWriter("C:\\Users\\Think\\Desktop\\a.txt");
BufferedWriter bw = new BufferedWriter(fw);

//寫資料
bw.write("hello Buffered");

bw.close();
}
}

使用起來感覺確實像是裝飾者模式,接下來看它們的結構:

小結

​ BufferedWriter使用裝飾者模式對Writer子實現類進行了增強,添加了緩衝區,提高了寫資料的效率。

6.代理和裝飾者的區別
靜態代理和裝飾者模式的區別:

相同點:
都要實現與目標類相同的業務介面
在兩個類中都要宣告目標物件
都可以在不修改目標類的前提下增強目標方法
不同點:
- 目的不同
裝飾者是為了增強目標物件
靜態代理是為了保護和隱藏目標物件
獲取目標物件構建的地方不同
裝飾者是由外界傳遞進來,可以通過構造方法傳遞
靜態代理是在代理類內部建立,以此來隱藏目標物件