C++ 设计模式 —— 组合模式
0. 引用连接
本文主要的思路和代码,来自于对以下连接的学习和实现:
组合模式
1. 引言
1.1 什么是组合模式?
- 组合模式的定义
- 组合模式的作用
组合模式是一种行为型设计模式,它将对象组合成树形结构以表示部分 - 整体的层次结构。这种模式使得用户对单个对象和组合对象的使用具有一致性。组合模式主要应用于需要表示复杂对象结构或者需要将对象组合成树形结构的场景。
组合模式的定义和作用:
定义:组合模式通过一种巧妙的设计方案,可以一致性地处理整个树形结构或者树形结构的一部分,也可以一致性地处理树形结构中的叶子节点(不包含子节点的节点)和容器节点。
作用:组合模式主要解决了在对象组合树中,如何实现一致性的操作和处理的问题。它使得用户可以方便地对对象树进行操作,而不需要考虑对象的具体类型。组合模式还提供了一种灵活的方式来表示对象结构,可以方便地添加和删除对象。
1.2 组合模式与其他设计模式的关系
组合模式是行为型设计模式的一种,它将对象组合成树形结构以表示部分 - 整体的层次结构。在实际应用中,组合模式与其他设计模式有着紧密的联系,常常共同出现在同一个解决方案中。以下是组合模式与其他设计模式的一些关系:
- 装饰者模式 : 装饰者模式与组合模式在结构上有相似之处,但它们的目的不同。装饰者模式通过动态地给一个对象添加职责,使其具有更多的功能,而组合模式关注的是将对象组合成树形结构以表示部分 - 整体的层次结构。两者都依赖于递归组合来组织大量对象。在某些情况下,装饰者模式和组合模式可以结合使用,以实现更加灵活的对象结构。
- 责任链模式 : 责任链模式用于处理具有多个处理步骤的问题,它将请求沿着处理器链进行传递。组合模式则关注将对象组合成树形结构以表示部分 - 整体的层次结构。虽然这两个模式的用途不同,但它们可以结合使用。例如,可以使用组合模式构建一个复杂的对象结构,然后使用责任链模式处理该结构中的请求。
- 迭代器模式 : 迭代器模式允许在不暴露底层数据结构的情况下遍历和访问数据。在组合模式中,可以使用迭代器来遍历复合对象的子元素。迭代器模式抽象了遍历组件的过程,确保每个元素都被访问到,同时不暴露底层数据结构的细节。
- 访问者模式 : 访问者模式允许在不改变对象结构的前提下定义新的操作。在组合模式中,可以将访问者模式应用于复合结构,以便在不更改现有类层次结构的情况下对元素执行操作。访问者模式使您可以在现有代码中添加新行为,而无需修改现有代码。
总之,在实际软件开发中,设计模式往往不会孤立存在。组合模式与其他设计模式之间的紧密联系使得开发人员可以根据具体需求和场景,将这些模式结合使用,以实现更加灵活、高效和可扩展的软件系统。
1.3 组合模式适用的场景
组合模式是一种行为型设计模式,适用于多种场景,如树形对象结构、统一对象处理、动态责任、可变责任和 UI 工具包等。在实际应用中,组合模式与其他设计模式密切相关,共同解决各种问题。以下是组合模式与其他设计模式的一些关系:
- 树形对象结构 : 组合模式适用于实现树形对象结构,如组织结构、文件系统、UI 元素等。这种结构可以方便地表示部分 - 整体的层次关系。在组合模式中,对象通过递归组合形成树状结构,使得客户代码可以统一处理简单元素和复杂元素。
- 统一对象处理 : 组合模式允许客户代码以统一的方式处理简单元素和复杂元素。这种统一处理方式可以简化客户端代码,提高代码的可维护性和可扩展性。通过组合模式,客户端可以忽略对象的复杂内部结构,而仅关注对象的整体行为。
- 动态责任 : 组合模式允许在运行时动态地为单个对象添加责任,而无需修改现有代码。这种动态责任分配可以提高系统的灵活性,使其能够适应不断变化的需求。在组合模式中,可以通过将新的责任分配给现有的对象来实现动态责任。
- 可变责任 : 组合模式适用于责任可能随时间变化的场景。在组合模式中,对象可以具有不同的责任,这使得系统能够适应不断变化的需求。通过组合模式,可以轻松地为对象添加新的责任,而无需修改现有代码。
- UI 工具包 : 组合模式在 UI 工具包中具有广泛的应用。在这种场景下,顶级 UI 元素由许多较小的独立底层 UI 元素组成,这些元素都响应相同的事件和操作。通过使用组合模式,可以轻松地管理 UI 元素的层次结构,并确保它们以一致的方式响应事件。
- 计费系统 : 在计费系统中,组合模式可以应用于处理复杂的活动记录。在这种场景下,重要的是能够生成正确的计费,而不关心活动的具体细节。通过使用组合模式,可以将复杂的活动记录组织成一个树形结构,以便生成正确的计费。
组合模式与其他设计模式密切相关,它们共同解决各种实际问题。在实际应用中,可以根据具体需求和场景,将组合模式与其他设计模式结合使用,以实现更加灵活、高效和可扩展的软件系统。
2. 组合模式的实现
2.1 组合模式概念伪代码
2.1.1 图形概念组合伪代码
CompoundGraphic
类是一个容器,可以包含任意数量的子形状,包括其他复合形状。复合形状具有与简单形状相同的方法。但是,与简单形状不同,复合形状会将请求递归地传递给所有子项,并将结果“求和”。
客户端代码通过与所有形状共享的单一接口与所有形状进行交互。因此,客户端不知道自己正在处理简单形状还是复合形状。客户端可以在不与组成该结构的具体类耦合的情况下处理非常复杂的对象结构。
代码结构分析:
- 定义复合图形类,包含初始化方法和递归调用方法。
- 定义简单图形类,包含初始化方法和基本操作方法。
- 定义客户端代码,通过与所有形状共享的单一接口与所有形状进行交互。
- 在客户端代码中,根据需要创建复合图形对象和简单图形对象。
- 使用复合图形对象和简单图形对象执行各种操作,如添加、删除、修改等。
- 验证客户端代码是否正确处理了复合图形对象和简单图形对象的操作。
// The component interface declares common operations for both
// simple and complex objects of a composition.
interface Graphic is
method move(x, y)
method draw()
// The leaf class represents end objects of a composition. A
// leaf object can't have any sub-objects. Usually, it's leaf
// objects that do the actual work, while composite objects only
// delegate to their sub-components.
class Dot implements Graphic is
field x, y
constructor Dot(x, y) { ... }
method move(x, y) is
this.x += x, this.y += y
method draw() is
// Draw a dot at X and Y.
// All component classes can extend other components.
class Circle extends Dot is
field radius
constructor Circle(x, y, radius) { ... }
method draw() is
// Draw a circle at X and Y with radius R.
// The composite class represents complex components that may
// have children. Composite objects usually delegate the actual
// work to their children and then "sum up" the result.
class CompoundGraphic implements Graphic is
field children: array of Graphic
// A composite object can add or remove other components
// (both simple or complex) to or from its child list.
method add(child: Graphic) is
// Add a child to the array of children.
method remove(child: Graphic) is
// Remove a child from the array of children.
method move(x, y) is
foreach (child in children) do
child.move(x, y)
// A composite executes its primary logic in a particular
// way. It traverses recursively through all its children,
// collecting and summing up their results. Since the
// composite's children pass these calls to their own
// children and so forth, the whole object tree is traversed
// as a result.
method draw() is
// 1. For each child component:
// - Draw the component.
// - Update the bounding rectangle.
// 2. Draw a dashed rectangle using the bounding
// coordinates.
// The client code works with all the components via their base
// interface. This way the client code can support simple leaf
// components as well as complex composites.
class ImageEditor is
field all: CompoundGraphic
method load() is
all = new CompoundGraphic()
all.add(new Dot(1, 2))
all.add(new Circle(5, 3, 10))
// ...
// Combine selected components into one complex composite
// component.
method groupSelected(components: array of Graphic) is
group = new CompoundGraphic()
foreach (component in components) do
group.add(component)
all.remove(component)
all.add(group)
// All components will be drawn.
all.draw()
2.1.2 叶子组合伪代码
组合模式是使用抽象基类或接口Component
实现的,它声明了公共操作。两个具体类Leaf
和Composite
继承自Component
。以下是这些概念的伪代码描述:
class Component {
public:
virtual void Add(Component a) { }
virtual void Remove() { }
virtual void Delete(Component a) { }
};
class Leaf : public Component {
public:
void Add(Component a) {
cout << "something is added to the leaf" << endl;
}
void Remove() {
cout << "something is removed from the leaf" << endl;
}
void Delete(Component a) {
cout << "something is deleted from leaf" << endl;
}
};
class Composite : public Component {
vector<Component> compositeComponents;
public:
void AddElement(Component a) {
compositeComponents.push_back(a);
}
void Add(Component a) {
cout << "something is added to the composite" << endl;
}
void Remove() {
cout << "something is removed from the composite" << endl;
}
void Delete(Component a) {
cout << "something is deleted from the composite";
}
};
在这个伪代码中,Component
是一个抽象基类,具有三个方法:Add
、Remove
和Delete
。Leaf
和Composite
类继承自Component
并实现这些方法。Composite
类还包含一个表示其子组件的Component
对象向量¹。
这种结构允许您统一处理单个对象(Leaf
)和对象的组合(Composite
)。
3. 组合模式的优点和缺点
Composite 模式的优点
- 它简化了与复合结构中对象交互的客户端代码。
- 它使得向复合结构添加新功能变得容易。
- 它使得表示分层数据结构变得容易。
Composite 模式的缺点
- 它可能会使复合对象的代码比简单对象的代码更复杂。
- 如果复合结构包含大量的子对象,它可能会降低复合结构的性能。
4. 代码实现
4.1 真实案例伪代码描述
Composite 模式的真实案例是树形数据结构,例如目录树。在这种情况下,抽象部分将表示一个目录,具体部分将表示文件或其他目录。以下是使用组合模式的伪代码示例:
# 抽象部分
class Directory:
def add_file(self, file):
pass
def remove_file(self, file):
pass
def list_files(self):
pass
# 具体部分
class File:
def __init__(self, name):
self.name = name
class DirectoryNode:
def __init__(self, directory):
self.directory = directory
self.children = []
def add_child(self, child):
self.children.append(child)
def remove_child(self, child):
self.children.remove(child)
def list_children(self):
for child in self.children:
print(child.name)
# 客户端代码
directory = Directory()
file1 = File("file1.txt")
file2 = File("file2.txt")
directory_node = DirectoryNode(directory)
directory_node.add_child(file1)
directory_node.add_child(file2)
directory.list_files() # 输出:file1.txt, file2.txt
在上述代码中,Directory
类是抽象部分,它定义了目录的基本操作,如添加文件、删除文件和列出文件。File
类是具体部分,表示一个文件对象。DirectoryNode
类是具体部分的组合体,它包含一个Directory
对象和一个子节点列表。通过调用add_child
和remove_child
方法,可以向目录树中添加或删除子节点。最后,客户端代码创建了一个目录对象和两个文件对象,并将这两个文件对象作为子节点添加到目录节点中。然后,通过调用list_files
方法,列出了目录中的所有文件。
4.2 概念示例代码
#include <iostream>
class Component {
public:
virtual void operation() = 0;
};
class Leaf : public Component {
public:
void operation() override {
std::cout << "I am a leaf." << std::endl;
}
};
class Composite : public Component {
public:
void operation() override {
std::cout << "I am a composite." << std::endl;
for (auto& child : children) {
child->operation();
}
}
这段代码展示了组合模式的实现。组合模式是一种结构型设计模式,它允许将对象组织成树形结构,使得客户端可以以统一的方式处理单个对象和组合对象。
代码中定义了一个抽象基类Component
,其中包含一个纯虚函数operation()
。这个函数在子类中被重写,用于执行具体的操作。
接下来定义了两个子类:Leaf
和Composite
。Leaf
类表示叶子节点,它从Component
类继承,并重写了operation()
函数,输出"I am a leaf.“。Composite
类表示复合节点,它也从Component
类继承,并重写了operation()
函数。在这个函数中,首先输出"I am a composite.”,然后通过循环遍历子节点(children
),对每个子节点调用operation()
函数。
通过组合模式,可以将叶子节点和复合节点统一对待,无论它们是单独使用还是作为其他节点的一部分。这种灵活性使得代码更加可扩展和易于维护。
4.2.1 2.1.1 概念代码实际代码
#include <iostream>
#include <vector>
class Graphic {
public:
virtual void move(int x, int y) = 0;
virtual void draw() = 0;
};
class Dot : public Graphic {
protected:
int x, y;
public:
Dot(int x, int y) : x(x), y(y) {}
void move(int x, int y) override {
this->x += x; this->y += y;
}
void draw() override {
std::cout << "Draw a dot at " << x << " and " << y << std::endl;
}
};
class Circle : public Dot {
protected:
int radius;
public:
Circle(int x, int y, int radius) : Dot(x, y), radius(radius) {}
void draw() override {
std::cout << "Draw a circle at " << x << " and " << y << " with radius " << radius << std::endl;
}
};
class CompoundGraphic : public Graphic {
protected:
std::vector<Graphic*> children;
public:
void add(Graphic* child) {
children.push_back(child);
}
void remove(Graphic* child) {
children.erase(std::remove(children.begin(), children.end(), child), children.end());
}
void move(int x, int y) override {
for (Graphic* child : children)
child->move(x, y);
}
void draw() override {
for (Graphic* child : children)
child->draw();
// Draw a dashed rectangle using the bounding coordinates.
// This part is left as an exercise.
}
};
class ImageEditor {
protected:
CompoundGraphic* all;
public:
void load() {
all = new CompoundGraphic();
all->add(new Dot(1, 2));
all->add(new Circle(5, 3, 10));
// ...
}
void groupSelected(std::vector<Graphic*> components) {
CompoundGraphic* group = new CompoundGraphic();
for (Graphic* component : components) {
group->add(component);
all->remove(component);
}
all->add(group);
all->draw();
}
};
这段代码定义了一个抽象基类Component
,其中包含三个方法:Add
、Remove
和Delete
。Leaf
和Composite
类继承自Component
并实现了这些方法。Composite
类还包含一个Component
指针的向量,表示其子组件。这种结构允许您统一处理单个对象(Leaf
)和对象的组合(Composite
)。
需要注意的是,这段代码是Composite Pattern的基本实现,并没有包含任何特定的功能。需要用自己逻辑替换占位符方法。同时,请记住在使用C++中的原始指针时正确管理内存。如果适用,可以考虑使用智能指针。
我们可以在任何支持C++11或更高版本的C++环境中编译和运行此代码。如果需要想查看这些方法的输出,需要创建Leaf
和Composite
的实例,调用它们的方法,并将输出打印到控制台。
4.3 真实案例代码
4.3.1 geom_exmaple.h
#ifndef _GEOM_EXAMPLE_H_
#define _GEOM_EXAMPLE_H_
#include <vector>
// The component interface declares common operations for both
// simple and complex objects of a composition.
class Graphic
{
public:
virtual void move(int x, int y) = 0;
virtual void draw() = 0;
};
// The leaf class represents end objects of a composition. A
// leaf object can't have any sub-objects. Usually, it's leaf
// objects that do the actual work, while composite objects only
// delegate to their sub-components.
class Dot : public Graphic
{
protected:
int x, y;
public:
Dot(int x, int y);
void move(int x, int y) override;
void draw() override;
};
// All component classes can extend other components.
class Circle : public Dot
{
private:
int radius;
public:
Circle(int x, int y, int radius);
void draw() override;
};
// The composite class represents complex components that may
// have children. Composite objects usually delegate the actual
// work to their children and then "sum up" the result.
class CompoundGraphic : public Graphic
{
private:
std::vector<Graphic*> children;
public:
void add(Graphic* child);
void remove(Graphic* child);
void move(int x, int y) override;
void draw() override;
};
// The client code works with all the components via their base
// interface. This way the client code can support simple leaf
// components as well as complex composites.
class ImageEditor
{
private:
CompoundGraphic all;
public:
void load();
// Combine selected components into one complex composite
// component.
void groupSelected(std::vector<Graphic*> components);
};
class GeomCompositeTest
{
public:
static void GeomCompositeExample();
};
#endif // _GEOM_EXAMPLE_H_
4.3.2 geom_example.cpp
#include "geom_example.h"
#include <iostream>
Dot::Dot(int x, int y)
: x(x), y(y)
{
}
void Dot::move(int x, int y)
{
this->x += x;
this->y += y;
}
void Dot::draw()
{
std::cout << "Draw a dot at (" << x << ", " << y << ")." << std::endl;
}
Circle::Circle(int x, int y, int radius)
: Dot(x, y), radius(radius)
{
}
void Circle::draw()
{
std::cout << "Draw a circle at (" << x << ", " << y << ") with radius " << radius << "." << std::endl;
}
void CompoundGraphic::add(Graphic* child)
{
children.push_back(child);
}
void CompoundGraphic::remove(Graphic* child)
{
for (auto it = children.begin(); it != children.end(); ++it) {
if (*it == child) {
children.erase(it);
break;
}
}
}
void CompoundGraphic::move(int x, int y)
{
for (Graphic* child : children) {
child->move(x, y);
}
}
void CompoundGraphic::draw()
{
std::cout << "1. For each child component:" << std::endl;
for (Graphic* child : children) {
child->draw(); // Draw the component.
// Update the bounding rectangle. (Not shown in the code.)
}
std::cout << "2. Draw a dashed rectangle using the bounding coordinates." << std::endl;
}
void ImageEditor::load()
{
all = CompoundGraphic();
all.add(new Dot(1, 2));
all.add(new Circle(5, 3, 10));
// ...
}
void ImageEditor::groupSelected(std::vector<Graphic*> components)
{
CompoundGraphic group;
for (Graphic* component : components) {
group.add(component);
all.remove(component);
}
all.add(&group);
// All components will be drawn.
all.draw();
}
void GeomCompositeTest::GeomCompositeExample()
{
ImageEditor editor;
editor.load();
std::vector<Graphic*> components;
components.push_back(new Dot(1, 2));
components.push_back(new Circle(5, 3, 10));
components.push_back(new Dot(7, 8));
components.push_back(new Circle(10, 11, 20));
editor.groupSelected(components);
}
4.4 代码分析
4.4.1 概念代码分析
4.4.1.1 4.2.1 代码分析
提供的C++代码是组合模式的实现,组合模式是一种设计模式,用于将一组对象以与单个对象的相同方式处理。组合模式的概念允许您将对象组合成树形结构,以表示部分-整体层次结构。
在此代码中,我们有以下类:
-
Component
:这是一个抽象基类,声明了简单和复杂对象的组合的共同操作。它声明了一个纯虚函数operation()
。 -
Leaf
:这个类代表组合的结束对象(叶子对象)。它从Component
类继承并实现了operation()
方法。在这个方法中,它只是打印出"I am a leaf."。 -
Composite
:这个类代表可能有子对象的复杂组件。它也从Component
类继承并实现了operation()
方法。在这个方法中,它首先打印出"I am a composite.",然后遍历其子组件并调用它们的operation()
方法。
这种结构允许您统一对待单个对象(Leaf
)和对象的组合(Composite
)。您可以对所有复合对象执行的操作通常具有最低公共分母关系。
请注意,这段代码是不完整的。Composite
类应该包含一个容器(如向量或列表)来保存其子组件,并提供添加或删除子组件的方法。此外,代码目前缺少与之交互的客户端。
4.4.1.2 4.2.2 代码分析
提供的C++代码是组合模式的一个实现,组合模式是一种设计模式,用于在需要以单个对象的方式处理一组对象的情况。组合模式的概念是允许您将对象组合成树形结构,以表示部分-整体层次结构。
在此代码中,我们有以下几个类:
-
Component
:这是一个抽象基类,声明了简单和复杂对象的共同操作。它声明了一个纯虚函数operation()
。它还具有设置和获取组件父级的方法和添加或删除子组件的方法。 -
Leaf
:这个类代表组合的结束对象(叶子对象)。它从Component
类继承并实现了operation()
方法。在此方法中,它只是打印出"I am a leaf."。 -
Composite
:这个类代表可能有子组件的复杂组件。它也从Component
类继承并实现了operation()
方法。在此方法中,它首先打印出"I am a composite.",然后遍历其子组件并调用它们的operation()
方法。
ClientCode
函数接受一个Component
对象并调用其operation()
方法。ClientCode2
函数接受两个Component
对象。如果第一个是一个复合对象,它将第二个添加为其子对象。然后它调用第一个的operation()
方法。
CompositeConceptionalExample
函数演示了如何使用这些类。它创建了一个简单的叶子组件和一个包含叶子节点和复合节点的复合树,然后将它们传递给客户端代码。
请注意,此代码未正确管理内存。它使用new
创建了几个对象,但没有使用delete
删除它们。在现实世界的代码中,应始终记住使用delete
删除用new
创建的对象,以防止内存泄漏。如果适用,请考虑使用智能指针。
4.4.2 真实案例代码分析
提供的代码是C++中组合模式的一个实现,它用于统一处理单个对象和对象的组成。代码分为两个文件:geom_example.h
(头文件)和geom_example.cpp
(实现文件)。
在geom_example.h
中,我们有以下几个类:
Graphic
:这是一个抽象基类,声明了组合中简单和复杂对象的常见操作。它声明了两个纯虚函数:move(int x, int y)
和draw()
。Dot
:这个类代表组合的结束对象(叶子对象)。它从Graphic
类继承并实现了move(int x, int y)
和draw()
方法。Circle
:这个类扩展了Dot
类,代表一个更复杂的对象,也可以是组合的一部分。它重写了Dot
类中的draw()
方法。CompoundGraphic
:这个类代表可能具有子组件的复杂组件。它也从Graphic
类继承并实现了add(Graphic* child)
、remove(Graphic* child)
、move(int x, int y)
和draw()
方法。ImageEditor
:这个类通过其基接口与所有组件一起工作。它具有加载组件和将选定的组件组合成一个复杂复合组件的方法。
在geom_example.cpp
中,这些类被实现:
Dot
和Circle
类有构造函数来初始化它们的特性,它们实现了在Graphic
接口中声明的move(int x, int y)
和draw()
方法。CompoundGraphic
类实现了添加或删除子组件、移动所有子组件和绘制所有子组件的方法。ImageEditor
类将组件加载到复合对象中,将选定的组件组合成一个新的复合对象,从旧的复合对象中删除它们,将新的复合对象添加到旧的复合对象中,并绘制所有组件。
这段代码演示了如何使用组合模式统一处理单个对象(Dot
和Circle
)和对象的组成(CompoundGraphic
)。
个人格言
追寻与内心共鸣的生活,未来会逐渐揭晓答案。
Pursue the life that resonates with your heart, and the future will gradually reveal the answer.