合成模式

<image>

在軟體工程中，合成模式（composite pattern），也翻譯為組成模式，是一種設計模式。合成模式描述了一組對象，按照對待相同對象類型的一個單一實例的方式處置它們。合成模式的意圖是將諸多對象合成（compose）入表示部份-整體層級的樹結構之中。實現合成模式使得客戶能統一的處置單獨對象和合成對象。

## 結構

<image>

在上面的UML類圖中：

- 客戶`Client`類不直接（單獨的）提及`Leaf`和`Composite`類，`Client`轉而提及公共的構件`Component`接口，並且統一的處置`Leaf`和`Composite`。
- 葉子`Leaf`類沒有子對象並且直接實現`Component`接口。
- 合成`Composite`類維護`Component`對象（`children`）的一個容器，並且將請求轉發到這些`children`（`for each child in children: child.operation()`）。

對象圖展示運行時交互：在這個例子中，客戶`Client`對象發送請求到樹結構的頂層合成`Composite`對象（具有類型 `Component`)。這個請求被沿著樹結構轉發到（辦理於）所有的子`Component`對象（`Leaf`和`Composite`對象)。

## 示例

### C++

下面的例子是基於《設計模式》書中先於C++98的樣例代碼的C++23實現。

```c++
import std;

using std::runtime_error;
using std::shared_ptr;
using std::string;
using std::unique_ptr;
using std::vector;

// Component object
// declares the interface for objects in the composition.
class Equipment {
private:
    string name;
    double netPrice;
protected:
    Equipment() = default;

    explicit Equipment(const string& name): 
        name{name}, netPrice{0} {}
public:
    // implements default behavior for the interface common to all classes, as appropriate.
    [[nodiscard]]
    virtual const string& getName() const noexcept {
        return name;
    }

    virtual void setName(const string& name) noexcept {
        this->name = name;
    }

    [[nodiscard]]
    virtual double getNetPrice() const noexcept {
        return netPrice;
    }

    virtual void setNetPrice(double netPrice) noexcept {
        this->netPrice = netPrice;
    }

    // declares an interface for accessing and managing its child components.
    virtual void add(shared_ptr<Equipment>) = 0;
    virtual void remove(shared_ptr<Equipment>) = 0;
    virtual ~Equipment() = default;
};

// Composite object
// defines behavior for components having children.
class CompositeEquipment: public Equipment {
private:
    // stores child components.
    using EquipmentList = vector<shared_ptr<Equipment>>;
    EquipmentList equipments;
protected:
    CompositeEquipment() = default;

    explicit CompositeEquipment(const string& name):
        Equipment(name), equipments{EquipmentList()} {}
public:
    // implements child-related operations in the Component interface.
    [[nodiscard]]
    virtual double getNetPrice() const noexcept override {
        double total = Equipment::getNetPrice();
        for (const Equipment& i: equipments) {
            total += i->getNetPrice();
        }
        return total;
    }

    virtual void add(shared_ptr<Equipment> equipment) override {
        equipments.push_back(equipment.get());
    }

    virtual void remove(shared_ptr<Equipment> equipment) override {
        equipments.remove(equipment.get());
    }
};

// Leaf object
// represents leaf objects in the composition.
class FloppyDisk: public Equipment {
public:
    explicit FloppyDisk(const String& name):
        Equipment(name) {}

    // A leaf has no children.
    void add(shared_ptr<Equipment>) override {
        throw runtime_error("FloppyDisk::add() cannot be called!");
    }

    void remove(shared_ptr<Equipment>) override {
        throw runtime_error("FloppyDisk::remove() cannot be called!");
    }
};

class Chassis: public CompositeEquipment {
public:
    explicit Chassis(const string& name): 
        CompositeEquipment(name) {}
};

int main() {
    shared_ptr<FloppyDisk> fd1 = std::make_shared<FloppyDisk>("3.5in Floppy");
    fd1->setNetPrice(19.99);
    std::println("{}: netPrice = {}", fd1->getName(), fd1->getNetPrice);

    shared_ptr<FloppyDisk> fd2 = std::make_shared<FloppyDisk>("5.25in Floppy");
    fd2->setNetPrice(29.99);
    std::println("{}: netPrice = {}", fd2->getName(), fd2->getNetPrice);

    unique_ptr<Chassis> ch = std::make_unique<Chassis>("PC Chassis");
    ch->setNetPrice(39.99);
    ch->add(fd1);
    ch->add(fd2);
    std::println("{}: netPrice = {}", ch->getName(), ch->getNetPrice);

    fd2->add(fd1);
}
```

程序的輸出為：

```output
3.5in Floppy: netPrice=19.99
5.25in Floppy: netPrice=29.99
PC Chassis: netPrice=89.97
terminate called after throwing an instance of 'std::runtime_error'
  what():  FloppyDisk::add
```

### Python

下面是Python的例子：

```python
from abc import ABC, abstractmethod

class Component(ABC):
    def __init__(self, name, price=0, **kwargs):
        self.name = name
        self.price = price
        for key, value in kwargs.items():
            self.__dict__[key] = value
    def get_name(self):
        return self.name
    def set_price(self, price):
        self.price = price
    def get_price(self):
        return self.price
    @abstractmethod
    def get_price(self): pass

class Leaf(Component):
    def get_price(self):
        return self.price

class Composite(Component):
    def __init__(self, *args, **kwargs):
        self.linked_list = []
        super().__init__(*args, **kwargs)
    def __len__(self):
        return len(self.linked_list)
    def __getitem__(self, index):
        return self.linked_list[index]
    def __setitem__(self, index, value):
        self.linked_list[index] = value
    def __delitem__(self, index):
        del self.linked_list[index]
    def add(self, equipment):
        self.linked_list.insert(0, equipment)
    def remove(self, equipment):
        self.linked_list.remove(equipment)
    def get_price(self):
        total = self.price
        for i in self.linked_list:
            total += i.get_price()
        return total

class ComputerCase(Composite): pass

class Chassis(Leaf): pass

class Motherboard(Composite): pass

class CPU(Leaf): pass

class RAM(Leaf): pass

class HardDisk(Leaf): pass

def demo():
    pc = ComputerCase("an assembled computer")
    pc.add(Chassis("Full-tower", 50))
    pc.add(Motherboard("ATX board", 180))
    pc[0].add(CPU("x86-64 CPU", 200))
    pc[0].add(RAM("DDR4", 150))
    pc[0].add(HardDisk("SSD", 250))
    print(f"The price of {pc.get_name()} is {pc.get_price()}.")
```

其執行：

```pycon
>>> demo()
The price of an assembled computer is 830.
```

## 外部連結

- Composite Pattern implementation in Java
- Composite pattern description from the Portland Pattern Repository
- Composite pattern in UML and in LePUS3, a formal modelling language
- Class::Delegation on CPAN
- "The End of Inheritance: Automatic Run-time Interface Building for Aggregated Objects" by Paul Baranowski
- PerfectJPattern Open Source Project , Provides componentized implementation of the Composite Pattern in Java
- A persistent Java-based implementation
- Composite Design Pattern