《Head First 設計模式》:裝飾者模式
正文
一、定義
裝飾者模式動態地將責任(功能)附加到對象上。若要擴展功能,裝飾者提供了比繼承更有彈性的替代方案。
要點:
- 裝飾者和被裝飾者有相同的超類型。
- 可以用一個或多個裝飾者包裝一個對象。
- 既然裝飾者和被裝飾者有相同的超類型,所以在任何需要原始對象(被裝飾者)的場合,都可以用裝飾過的對象代替它。
- 裝飾者可以在被裝飾者的行為之前與/或之後,加上自己的行為,甚至將被裝飾者的行為整個取代掉,以到達特定的目的。
- 對象可以在任何時候被裝飾,所以可以在運行時動態地、不限量地用裝飾者裝飾對象。
- 裝飾者會導致設計中出現許多小對象,如果過度使用,會讓程序變得很複雜。
二、實現步驟
1、創建組件接口
裝飾者和被裝飾者都必須實現組件接口。
也可以用組件抽象類,然後讓裝飾者和被裝飾者繼承組件抽象類,只要裝飾者和被裝飾者具有相同的超類型即可。
/**
* 組件接口(裝飾者和被裝飾者都必須實現該接口)
*/
public interface Component {
public void doSomething();
}
2、創建具體的組件,並實現組件接口
/**
* 具體組件(被裝飾者)
*/
public class ConcreteComponent implements Component {
@Override
public void doSomething() {
System.out.println("ConcreteComponent do something...");
}
}
3、創建裝飾者抽象類,並實現組件接口
如果只有一個裝飾者,也可以不創建裝飾者抽象類,而是由具體的裝飾者直接實現組件接口
/**
* 組件裝飾者抽象類
*/
public abstract class ComponentDecorator implements Component {
protected Component component;
public ComponentDecorator(Component component) {
// 通過構造傳入組件(被裝飾者)
this.component = component;
}
@Override
public void doSomething() {
// 委託給組件(被裝飾者)
component.doSomething();
}
}
4、創建具體的裝飾者,並繼承裝飾者抽象類
(1)裝飾者 A
/**
* 裝飾者A
*/
public class ComponentDecoratorA extends ComponentDecorator {
public ComponentDecoratorA(Component component) {
super(component);
}
@Override
public void doSomething() {
// 裝飾者添加自己的業務代碼
component.doSomething();
// 裝飾者添加自己的業務代碼
System.out.println("ComponentDecoratorA do something...");
}
}
(2)裝飾者 B
/**
* 裝飾者B
*/
public class ComponentDecoratorB extends ComponentDecorator {
public ComponentDecoratorB(Component component) {
super(component);
}
@Override
public void doSomething() {
// 裝飾者添加自己的業務代碼
component.doSomething();
// 裝飾者添加自己的業務代碼
System.out.println("ComponentDecoratorB do something...");
}
}
5、使用裝飾者裝飾組件
public class Test {
public static void main(String[] args) {
// 具體組件(被裝飾者)
Component component = new ConcreteComponent();
// 用裝飾者A裝飾組件
ComponentDecorator componentDecoratorA = new ComponentDecoratorA(component);
// 用裝飾者B裝飾組件
ComponentDecorator componentDecoratorB = new ComponentDecoratorB(component);
component.doSomething();
componentDecoratorA.doSomething();
componentDecoratorB.doSomething();
}
}
三、舉個栗子
1、背景
星巴茲是以擴張速度最快而聞名的咖啡連鎖店。因為擴張速度實在太快了,他們準備更新訂單系統,以合乎他們的飲料供應要求——
顧客在購買咖啡時,可以要求在其中加入各種調料,例如:蒸奶、豆漿、摩卡(巧克力風味)或覆蓋奶泡。星巴茲會根據所加入的調料收取不同的費用。所以訂單系統必須考慮到這些調料部分。
2、實現
把調料理解為飲料裝飾者,然後以飲料為主體,用調料來「裝飾」飲料。
(1)創建飲料抽象類
/**
* 飲料抽象類(組件)
*/
public abstract class Beverage {
public String description = "Unknown Beverage";
/**
* 描述
*/
public String getDescription() {
return description;
}
/**
* 價格
*/
public abstract double cost();
}
(2)創建具體的飲料,並繼承飲料抽象類
/**
* 濃縮咖啡
*/
public class Espresso extends Beverage {
public Espresso() {
description = "Espresso";
}
@Override
public double cost() {
return 1.99;
}
}
/**
* 綜合咖啡
*/
public class HouseBlend extends Beverage {
public HouseBlend() {
description = "House Blend Coffee";
}
@Override
public double cost() {
return 0.89;
}
}
(3)創建調料抽象類,並繼承飲料抽象類
/**
* 調料抽象類(裝飾者抽象類)
*/
public abstract class CondimentDecorator extends Beverage {
@Override
public abstract String getDescription();
}
(4)創建具體的調料,並繼承調料抽象類
/**
* 摩卡(裝飾者)
*/
public class Mocha extends CondimentDecorator {
Beverage beverage;
public Mocha(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Mocha";
}
@Override
public double cost() {
// 加上摩卡的價格
return beverage.cost() + 0.20;
}
}
/**
* 豆漿(裝飾者)
*/
public class Soy extends CondimentDecorator {
Beverage beverage;
public Soy(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Soy";
}
@Override
public double cost() {
// 加上豆漿的價格
return beverage.cost() + 0.15;
}
}
/**
* 奶泡(裝飾者)
*/
public class Whip extends CondimentDecorator {
Beverage beverage;
public Whip(Beverage beverage) {
this.beverage = beverage;
}
@Override
public String getDescription() {
return beverage.getDescription() + ", Whip";
}
@Override
public double cost() {
// 加上奶泡的價格
return beverage.cost() + 0.10;
}
}
(5)測試
public class Test {
public static void main(String[] args) {
// 濃縮咖啡
Beverage beverage = new Espresso();
System.out.println(beverage.getDescription() + " $" + beverage.cost());
// 綜合咖啡
Beverage beverage2 = new HouseBlend();
System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
// 添加摩卡
beverage2 = new Mocha(beverage2);
// 添加豆漿
beverage2 = new Soy(beverage2);
// 添加奶泡
beverage = new Whip(beverage2);
System.out.println(beverage2.getDescription() + " $" + beverage2.cost());
}
}