Composite模式
计算机科学中广泛使用的数据结构是树结构,到处都可以找到树结构,例如,数据媒体(比如硬盘)上的文件系统,它的分层组织就符合树的结构。集成开发环境(IDE)的项目浏览器通常也具有树结构。在编译器设计中,用到一种叫作抽象语法树(AST)的方法,顾名思义,它是指以树状结构表示源代码的抽象语法结构,抽象语法树通常是编译器在语法分析阶段的结果。
对树状数据结构的面向对象的蓝图被称为组合模式。该模式的任务说明如下:
将对象组合成树结构来表示“部分——整体”的层次结构。组合允许客户端统一地处理单个对象和对象的组合。
我们在Command和Command处理器种的示例可以扩展为复合Command,并且Command还可以记录和重放。所以我们在之前的设计中添加了一个新类,一个CompositeCommand:
#pragma once
#include"Command.h"
#include<vector>
class CompositeCommand :public UndoableCommand
{
public:
void addCommand(CommandPtr& command)
{
commands.push_back(command);
}
virtual void execute() override
{
for (const auto& command:commands)
{
command->execute();
}
}
virtual void undo() override
{
for (const auto& command : commands)
{
command->undo();
}
}
private:
std::vector<CommandPtr> commands;
};
CompositeCommand有一个成员函数addCommand(),它允许你将命令添加到CompositeCommand的实例。由于CompositeCommand类也实现了UndoableCommand接口,因此可以将其实例视为普通的command。换句话说,我们可以以其他的CompositeCommand来分层地组合出一个新的CompositeCommand。通过Composite模式的递归结构,你可以生成command树。
现在可以使用新添加的类CompositeCommand作为宏录制器,以便记录和重放command序列:
int main()
{
CommandProcessor commandProcessor{};
DrawingProcessor drawingProcessor{};
auto macroRecorder = std::make_shared<CompositeCommand>();
Point circleCenterPoint{ 20,20 };
CommandPtr drawCircleCommand = std::make_shared<DrawingCircleCommand>(drawingProcessor,
circleCenterPoint, 10);
commandProcessor.execute(drawCircleCommand);
macroRecorder->addCommand(drawCircleCommand);
Point rectangleCenterPoint{ 30, 10 };
CommandPtr drawRectangleCommand = std::make_shared<DrawRectangleCommand>(drawingProcessor,
rectangleCenterPoint, 5, 8);
commandProcessor.execute(drawRectangleCommand);
macroRecorder->addCommand(drawRectangleCommand);
commandProcessor.execute(macroRecorder);
CommandPtr undoCommand = std::make_shared<UndoCommand>(commandProcessor);
commandProcessor.execute(undoCommand);
return 0;
在Composite模式的帮助下,现在我们很容易把简单的command组装成复杂的command序列(前者被称为“叶子”)。由于guancCompositeCommand还实现了UndoableCommand接口,因此它们可以像简单的Command一样被使用。这极大地简化了客户端的代码。
仔细观察会发现上面还有一个缺点。在使用类CompositeCommand的具体实例(macroRecorder)时才能访问成员函数addCommand(),而通过UndoableCommand接口无法使用该成员函数。换句话说,这里的组合类和叶子的地位并不平等(记住模式的意图)!
通用Composite模式中,管理子元素的函数是在抽象中声明的。于是在我们的例子中,这意味我们必须在接口UndoableCommand中声明一个addCommand()(顺便说一句,这将违反ISP)。这一致命的后果是叶子元素必须覆盖addCommand(),并且必须为这个成员函数提供有意义的实现。但这是不可能的!如果我们向DrawCircleCommand的实例添加一个Command,假使不违反最少惊讶原则,会出现什么问题呢?
如果这样做,那将违反里氏替换原则(LSP)。因此,对案例进行权衡并且区别对待组合类和叶子是较好的选择。