修飾模式

<image>

在面向對象編程領域中，修飾模式（decorator pattern），是一種動態地往一個類別中添加新的行為的設計模式。就功能而言，修飾模式相比生成子類別更為靈活，這樣可以給某個對象而不是整個類別添加一些功能。

## 介紹

通過使用修飾模式，可以在運行時擴充一個類別的功能。原理是：增加一個修飾類包裹原來的類別，包裹的方式是在修飾類的構造函數中將原來的類以參數的形式傳入。裝飾類實現新的功能，但是，在不需要用到新功能的地方，它可以直接調用原來的類別中的方法。修飾類必須和原來的類別有相同的接口。

修飾模式是類別繼承的另外一種選擇。類繼承在編譯時候增加行為，而裝飾模式是在運行時增加行為。

當有幾個相互獨立的功能需要擴充時，這個區別就變得很重要。在有些面向對象的程式語言中，類別不能在運行時被創建，通常在設計的時候也不能預測到有哪幾種功能組合。這就意味著要為每一種組合創建一個新類別。相反，修飾模式是面向運行時候的對象實例的,這樣就可以在運行時根據需要進行組合。修飾模式的典型的應用，是Java里的I/O Streams的實現。

## 動機

<image>

舉個例子來說，在一個視窗軟體系統中呈現的一個視窗，為了讓視窗中呈現的內容資料能夠捲動，人們希望給它添加水平或垂直滾動條。假設窗口通過`Window`類實例來表示，並且假設它沒有添加滾動條功能。人們可以創建一個子類`ScrollingWindow`來提供，或者可以創建一個`ScrollingWindowDecorator`來為已存在的`Window`對象添加這個功能。在這點上，只要是解決方案就可以了。

假設人們希望選擇給的窗口添加邊框，同樣，原始`Window`類不支持。`ScrollingWindow`子類現在會造成一個問題，因為它會有效的創建一種新的窗口。如果人們想要給所有窗口添加邊框，必須創建`WindowWithBorder`和`ScrollingWindowWithBorder`子類。顯然，這個問題由於被添加類而變得更糟了。對於修飾模式，簡單的創建一個新類`BorderedWindowDecorator`，在運行時，能夠使用`ScrollingWindowDecorator`或`BorderedWindowDecorator`或兩者結合來修飾已存在的窗口。

一個修飾能夠被應用的另一個好例子是當有需要根據某套規則或者幾個平行的規則集（不同的使用者憑據等）限制訪問對象的屬性或方法時。一個對象的屬性或方法按照某組規則或幾個並行規則(不同使用者證書等)需要限制訪問時，在這種情況下，不是在原始對象中實現訪問控制而是在他的使用中不變或不知道任何限制，並且他被包裝在一個訪問控制修飾對象中，這個對象能夠對允許的原始對象的接口子集服務。

## 結構

<image>

在上面的UML類圖中：

- 抽象類`Decorator`維護到被修飾的對象（`Component`）的引用（`component`）並轉發請求至它（`component.operation()`）。這使得`Decorator`透明（不可見）於`Component`的客戶。
- 子類（`Decorator1`,`Decorator2`），實現應當增加到`Component`的額外行為（`addBehavior()`）於轉發請求給它之前/之後。

序類圖展示運行時交互：

- `Client`對象協作於`Decorator1`和`Decorator2`對象來擴展`Component1`對象的功能。
- `Client`調用`operation()`於`Decorator1`之上，它轉發請求至`Decorator2`。`Decorator2`在轉發請求至`Component1`之後進行進行`addBehavior()`，接著返回到`Decorator1`，它進行`addBehavior()`並返回到`Client`。

## 代碼示例

### C++

下面是基於《設計模式》書中前C++98實現的C++23實現例子：

```c++
import std;

using std::unique_ptr;

// Beverage interface.
class Beverage {
public:
    virtual void drink() = 0;
    virtual ~Beverage() = default;
};

// Drinks which can be decorated.
class Coffee: public Beverage {
public:
    virtual void drink() override {
        std::print("Drinking Coffee");
    }
};

class Soda: public Beverage {
public:
    virtual void drink() override {
        std::print("Drinking Soda");
    }
};

class BeverageDecorator: public Beverage {
private:
    unique_ptr<Beverage> component;
protected:
    void callComponentDrink() {
        if (component) {
            component->drink();
        }
    }
public:
    BeverageDecorator() = delete;

    explicit BeverageDecorator(unique_ptr<Beverage> component): 
        component{std::move(component)} {}
  
    virtual void drink() = 0;
};

class Milk: public BeverageDecorator {
private:
    float percentage;
public:
    Milk(unique_ptr<Beverage> component, float percentage):
        BeverageDecorator(std::move(component)), percentage{percentage} {}

    virtual void drink() override {
        callComponentDrink();
        std::print(", with milk of richness {}%", percentage);
    }
};

class IceCubes: public BeverageDecorator {
private:
    int count;
public:
    IceCubes(unique_ptr<Beverage> component, int count):
        BeverageDecorator(std::move(component)), count{count} {}

    virtual void drink() override {
        callComponentDrink();
        std::print(", with {} ice cubes", count);
    }
};

class Sugar: public BeverageDecorator {
private:
    int spoons = 1;
public:
    Sugar(unique_ptr<Beverage> component, int spoons):
        BeverageDecorator(std::move(component)), spoons{spoons} {}
  
    virtual void drink() override {
        callComponentDrink();
        std::print(", with {} spoons of sugar", spoons);
    }
};

int main(int argc, char* argv[]) { 
    unique_ptr<Beverage> soda = std::make_unique<Soda>();
    soda = std::make_unique<IceCubes>(std::move(soda), 3);
    soda = std::make_unique<Sugar>(std::move(soda), 1);

    soda->drink();
    std::println();
  
    unique_ptr<Beverage> coffee = std::make_unique<Coffee>();
    coffee = std::make_unique<IceCubes>(std::move(coffee), 16);
    coffee = std::make_unique<Milk>(std::move(coffee), 3.);
    coffee = std::make_unique<Sugar>(std::move(coffee), 2);

    coffee->drink();

    return 0;
}
```

程序的輸出如下：

```output
Drinking Soda, with 3 ice cubes, with 1 spoons of sugar
Drinking Coffee, with 16 ice cubes, with milk of richness 3%, with 2 spoons of sugar
```

### Java

這個Java示例使用window/scrolling情境。

```java
// The Window interface class
public interface Window {
	public void draw(); // Draws the Window
	public String getDescription(); // Returns a description of the Window
}

// implementation of a simple Window without any scrollbars
public class SimpleWindow implements Window {
	public void draw() {
		// Draw window
	}

	public String getDescription() {
		return "simple window";
	}
}
```

以下類包含所有Window類的decorator，以及修飾類本身。

```java
// abstract decorator class - note that it implements Window
public abstract class WindowDecorator implements Window {
    protected Window decoratedWindow; // the Window being decorated

    public WindowDecorator (Window decoratedWindow) {
        this.decoratedWindow = decoratedWindow;
    }
    
    @Override
    public void draw() {
        decoratedWindow.draw();
    }

    @Override
    public String getDescription() {
        return decoratedWindow.getDescription();
    }
}

// The first concrete decorator which adds vertical scrollbar functionality
public class VerticalScrollBar extends WindowDecorator {
	public VerticalScrollBar(Window windowToBeDecorated) {
		super(windowToBeDecorated);
	}

	@Override
	public void draw() {
		super.draw();
		drawVerticalScrollBar();
	}

	private void drawVerticalScrollBar() {
		// Draw the vertical scrollbar
	}

	@Override
	public String getDescription() {
		return super.getDescription() + ", including vertical scrollbars";
	}
}

// The second concrete decorator which adds horizontal scrollbar functionality
public class HorizontalScrollBar extends WindowDecorator {
	public HorizontalScrollBar (Window windowToBeDecorated) {
		super(windowToBeDecorated);
	}

	@Override
	public void draw() {
		super.draw();
		drawHorizontalScrollBar();
	}

	private void drawHorizontalScrollBar() {
		// Draw the horizontal scrollbar
	}

	@Override
	public String getDescription() {
		return super.getDescription() + ", including horizontal scrollbars";
	}
}
```

以下是一個測試程序，它創建了一個包含多重裝飾的Window實例(如,包含了垂直的和水平的滾動條),然後輸出它的描述：

```java
public class Main {
	// for print descriptions of the window subclasses
	static void printInfo(Window w) {
		System.out.println("description:"+w.getDescription());
	}
	public static void main(String[] args) {
		// original SimpleWindow
		SimpleWindow sw = new SimpleWindow();
		printInfo(sw);
		// HorizontalScrollBar  mixed Window
		HorizontalScrollBar hbw = new HorizontalScrollBar(sw);
		printInfo(hbw);
		// VerticalScrollBar mixed Window
		VerticalScrollBar vbw = new VerticalScrollBar(hbw);
		printInfo(vbw);
	}
}
```

以下是SimpleWindow及添加了組件HorizontalScrollBar和VerticalScrollBar後的Window測試結果：

```bash
description:simple window
description:simple window, including horizontal scrollbars
description:simple window, including horizontal scrollbars, including vertical scrollbars
```

### C#

C#代碼示例:

```csharp
namespace DecoratorExample;

interface IBike {
    string GetDetails();
    double GetPrice();
}

class AluminiumBike : IBike {
    public double GetPrice() => 100.0;

    public string GetDetails() => "Aluminium Bike";
}

class CarbonBike : IBike {
    public double GetPrice() => 1000.0;

    public string GetDetails() => "Carbon";
}

abstract class BikeAccessories : IBike {
    private readonly IBike _bike;

    public BikeAccessories(IBike bike) {
        _bike = bike;
    }

    public virtual double GetPrice() => _bike.GetPrice();

    public virtual string GetDetails() => _bike.GetDetails();
}

class SecurityPackage : BikeAccessories {
    public SecurityPackage(IBike bike) : base(bike) {
        // ...
    }

    public override string GetDetails() => $"{base.GetDetails()} + Security Package";

    public override double GetPrice() => base.GetPrice() + 1;
}

class SportPackage : BikeAccessories {
    public SportPackage(IBike bike) : base(bike) {
        // ...
    }

    public override string GetDetails() => $"{base.GetDetails()} + Sport Package";

    public override double GetPrice() => base.GetPrice() + 10;
}

public class BikeShop {
    public void UpgradeBike() {
        AluminiumBike basicBike = new AluminiumBike();
        BikeAccessories upgraded = new SportPackage(basicBike);
        upgraded = new SecurityPackage(upgraded);

        Console.WriteLine($"Bike: '{upgraded.GetDetails()}' Cost: {upgraded.GetPrice()}");
    }

    static void Main(string[] args) {
        UpgradeBike();
    }
}
```

其輸出：

```output
Bike: 'Aluminium Bike + Sport Package + Security Package' Cost: 111
```

### Python

下面的Python例子，通過咖啡製作場景展示如何使用修飾者，這個例子的場景中僅包含費用和配料。

```python
from abc import ABC, abstractmethod

class Component(ABC):
    @abstractmethod
    def cost(self): pass
    @abstractmethod
    def ingredients(self): pass
    def info(self):
        print(f'Cost: {self.cost()}; '
            + f'Ingredients: {self.ingredients()}')

class Coffee(Component):
    def cost(self):
        return 1
    def ingredients(self):
        return 'Coffee'

class Decorator(Component):
    def __init__(self, component):
        self.component = component
    def cost(self):
        return self.component.cost()
    def ingredients(self):
        return self.component.ingredients()

class WithMilk(Decorator):
    def cost(self):
        return super().cost() + 0.5
    def ingredients(self):
        return f'{super().ingredients()}, Milk'

class WithSprinkles(Decorator):
    def cost(self):
        return super().cost() + 0.2
    def ingredients(self):
        return f'{super().ingredients()}, Sprinkles'
```

其執行：

```pycon
>>> coffee = Coffee()
>>> coffee.info()
Cost: 1; Ingredients: Coffee
>>> coffee = WithMilk(coffee)
>>> coffee.info()
Cost: 1.5; Ingredients: Coffee, Milk
>>> coffee = WithSprinkles(coffee)
>>> coffee.info()
Cost: 1.7; Ingredients: Coffee, Milk, Sprinkles
```

下面的Python例子，在0-255的隨機數的8x10方陣中，展示了通過修飾者流水線，給對象動態的附加上行為：

```python
from abc import ABC, abstractmethod
from random import Random

class RandomSquare():
    def __init__(self, row, col, seed=None):
        self.random = Random(seed)
        self.row = row
        self.col = col
    def __call__(self, x, y):
        assert 0 <= x < self.row and 0 <= y < self.col
        return self.random.randint(0, 255)

class Decorator(ABC):
    def __init__(self, previous):
        self.previous = previous
        self.component = (previous.component
            if hasattr(previous, 'component') else previous)
    @abstractmethod
    def __call__(self): pass

class CacheValue(Decorator):
    def __init__(self, previous):
        super().__init__(previous)
        n = self.component.col * self.component.row
        self.cache = [None]*n
    def __call__(self, x, y):
        i = self.component.col*x + y
        r = self.cache[i]
        if r is None:
            r = self.previous(x, y)
            self.cache[i] = r
        return r

class MinMaxValue(Decorator):
    def __init__(self, previous, minval, maxval):
        super().__init__(previous)
        self.minval = minval
        self.maxval = maxval
    def __call__(self, x, y):
        r = self.previous(x, y)
        r = (r if r > self.minval else self.minval)
        return (r if r < self.maxval else self.maxval)

class Visibility(Decorator):
    def __call__(self, x, y):
        return self.previous(x, y)
    def draw(self):
        for x in range(self.component.row):
            for y in range(self.component.col):
                print(f"{self.__call__(x, y):3d}", end=' ')
            print()

square = RandomSquare(8, 10)
square = CacheValue(square)
square = MinMaxValue(square, 55, 200)
square = Visibility(square)
```

其執行：

```pycon
>>> square(0, 0)
62
>>> square.draw()
 62 115  55 200  55 200 119 179  94 200 
200  88 115 200 200 200 200 113  55 157 
 55 200 200 200 124 183 195  55 200 188 
 55 200  55 200 183 150 127 200 200 200 
 90 200  55  89 168 183 165 112 200 110 
 55 200  55  88 103 200  55 200 157 114 
 55  55  81 194  83  59 200 200 112 200 
177  55  55 167 200 126 200 178  55 161 
```

## 外部連結

- Article " The Decorator Design Pattern" (Java) by Antonio García and Stephen Wong
- Article " Using the Decorator Pattern" (Java) by Budi Kurniawan
- Sample Chapter " C# Design Patterns: The Decorator Pattern" (C#) by James W. Cooper
- A Delphi approach (Delphi)
- A JavaScript implementation
- Jt J2EE Pattern Oriented Framework
- Decorator Pattern with Ruby in 8 Lines (Ruby)