0%

Use std::variant to represent composite design pattern

最近摸索出來的心得,先來看看傳統的 Composite design pattern要怎麼作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <vector>
#include <memory>
using namespace std;

class Component
{
public:
virtual ~Component() = default;
};

class Leaf : public Component
{
};

class Composite : public Component
{
vector<unique_ptr<Component>> children;
public:
void add(Component *ele)
{
children.push_back(std::unique_ptr<Component>(ele));
}
};

這個方法不差,不過還是有幾點可以改良的
– 每個Subtype都必須繼承Component,就算沒有任何is-a的關聯性還是必須這麼作
– 當要Clone一份物件出來的話,需要另外一個Prototype Pattern
例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Component
{
public:
virtual ~Component() = default;
virtual Component* clone() = 0;
};

class Leaf : public Component
{
public:
Component* clone() override { return new Leaf(); }
};

class Composite : public Component
{
vector<unique_ptr<Component>> children;
public:
void add(Component *ele)
{
children.push_back(std::unique_ptr<Component>(ele));
}
Component* clone() override {
Composite* composite = new Composite();
for (const auto &child : children)
composite->children.push_back(
std::unique_ptr<Component>(child->clone()));
return composite;
}
};

每個都這麼作實在很醜,並且又容易錯

Solution based on std::variant

使用std::variant可以解決上面的問題,乾淨俐落

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Leaf;
class Composite;
using CompositeVar = variant<Leaf, Composite>;
class Leaf final
{
public:
Leaf() = default;
Leaf(const Leaf&) = default;
};

class Composite final
{
vector<CompositeVar> children;
public:
Composite() = default;
Composite(const Composite&) = default;
void add(const CompositeVar &ele)
{
children.push_back(ele);
}
};

不過這也不是萬用解,當你的CompositeVar 中的type數目是有限的,可以採用這個方式
不然就是每加入一種Type,就必須重新編譯一次,反而沒有舊版的彈性