修饰模式

<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)