裝飾器模式(Decorator Pattern)允許向一個(gè)現(xiàn)有的對(duì)象添加新的功能,同時(shí)又不改變其結(jié)構(gòu)。這種類型的設(shè)計(jì)模式屬于結(jié)構(gòu)型模式,它是作為現(xiàn)有的類的一個(gè)包裝。
這種模式創(chuàng)建了一個(gè)裝飾類,用來包裝原有的類,并在保持類方法簽名完整性的前提下,提供了額外的功能。
我們通過下面的實(shí)例來演示裝飾器模式的用法。其中,我們將把一個(gè)形狀裝飾上不同的顏色,同時(shí)又不改變形狀類。
介紹
裝飾器實(shí)現(xiàn)修飾對(duì)象(Component)的接口,所有請(qǐng)求都轉(zhuǎn)發(fā)給它處理,在轉(zhuǎn)發(fā)請(qǐng)求之前/之后增加額外功能。使用步驟是:
用一個(gè)Decorator實(shí)現(xiàn)/繼承需要修飾的對(duì)象Component;
在Decorator中增加一個(gè)Component的引用;
在Decorator的構(gòu)造器中,增加一個(gè)Component參數(shù)來初始化Component;
在Decorator類中,使用Component的引用,將所有請(qǐng)求轉(zhuǎn)發(fā)至Component的相應(yīng)方法;
ConcreteDecorator中所有Override自Component的方法做相應(yīng)調(diào)整。
從類圖上看,裝飾器模式與代理模式很像,是它們的目的不同,所以使用方法和適用場(chǎng)景上也就不同 ,裝飾器模式與代理模式的區(qū)別:
代理模式專注于對(duì)被代理對(duì)象的訪問;
裝飾器模式專注于對(duì)被裝飾對(duì)象附加額外功能。
就像前面所說的io工具包,我用BufferedInputStream和用FileInputStream去read一個(gè)文件實(shí)際使用方式上是一樣的,能用FileInputStream.read(),就能用BufferedInputStream.read(),只不過,BufferedInputStream把FileInputStream包裝了一下,增加了一個(gè)緩存,并不控制底層FileInputStream的read()行為。
適用場(chǎng)景
運(yùn)行時(shí),你需要?jiǎng)討B(tài)地為對(duì)象增加額外職責(zé)時(shí);
當(dāng)你需要一個(gè)能夠代替子類的類,借助它提供額外方法時(shí)
代碼實(shí)現(xiàn)
假設(shè)我去買咖啡,首先服務(wù)員給我沖了一杯原味咖啡,我希望服務(wù)員給我加些牛奶和白糖混合入原味咖啡中。使用裝飾器模式就可以解決這個(gè)問題。
咖啡接口,定義了獲取花費(fèi)和配料的接口。
/**
* 咖啡
*/
interface Coffee {
/** 獲取價(jià)格 */
double getCost();
/** 獲取配料 */
String getIngredients();
}
原味咖啡,實(shí)現(xiàn)Coffe接口,花費(fèi)1元,配料中,只有咖啡
/**
* 原味咖啡
*/
class SimpleCoffee implements Coffee {
@Override
public double getCost() {
return 1;
}
@Override
public String getIngredients() {
return "Coffee";
}
}
咖啡對(duì)象的裝飾器類,同樣實(shí)現(xiàn)Coffee接口,定義一個(gè)Coffe對(duì)象的引用,在構(gòu)造器中進(jìn)行初始化。并且將getCost()和getIntegredients()方法轉(zhuǎn)發(fā)給被裝飾對(duì)象。
/**
* 咖啡的"裝飾器",可以給咖啡添加各種"配料"
*/
abstract class CoffeeDecorator implements Coffee {
protected final Coffee decoratedCoffee;
/**
* 在構(gòu)造方法中,初始化咖啡對(duì)象的引用
*/
public CoffeeDecorator(Coffee coffee) {
decoratedCoffee = coffee;
}
/**
* 裝飾器父類中直接轉(zhuǎn)發(fā)"請(qǐng)求"至引用對(duì)象
*/
public double getCost() {
return decoratedCoffee.getCost();
}
public String getIngredients() {
return decoratedCoffee.getIngredients();
}
}
具體的裝飾器類,負(fù)責(zé)往咖啡中“添加”牛奶,注意看getCost()方法和getIngredients()方法,可以在轉(zhuǎn)發(fā)請(qǐng)求之前或者之后,增加功能。如果是代理模式,這里的結(jié)構(gòu)就有所不同,通常代理模式根據(jù)運(yùn)行時(shí)的條件來判斷是否轉(zhuǎn)發(fā)請(qǐng)求。
/**
* 此裝飾類混合"牛奶"到原味咖啡中
*/
class WithMilk extends CoffeeDecorator {
public WithMilk(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
double additionalCost = 0.5;
return super.getCost() + additionalCost;
}
@Override
public String getIngredients() {
String additionalIngredient = "milk";
return super.getIngredients() + ", " + additionalIngredient;
}
}
另一個(gè)具體裝飾器類,用來給咖啡加糖,一樣的邏輯。
class WithSugar extends CoffeeDecorator {
public WithSugar(Coffee coffee) {
super(coffee);
}
@Override
public double getCost() {
return super.getCost() + 1;
}
@Override
public String getIngredients() {
return super.getIngredients() + ", Sugar";
}
}
客戶端使用裝飾器模式,是不是與java中的io使用方式很像?
public class DecoratorDemo {
static void print(Coffee c) {
System.out.println("花費(fèi)了: " + c.getCost());
System.out.println("配料: " + c.getIngredients());
System.out.println("============");
}
public static void main(String[] args) {
//原味咖啡
Coffee c = new SimpleCoffee();
print(c);
//增加牛奶的咖啡
c = new WithMilk(c);
print(c);
//再加一點(diǎn)糖
c = new WithSugar(c);
print(c);
}
}