泰州住房和城乡建设网站,wordpress物流企业主题,如何在凡科建设网站,做网站模块设计模式21-组合模式#xff08;Composite Pattern#xff09; 写在前面 动机定义与结构定义结构主要类及其关系 C代码推导优缺点应用场景总结补充叶子节点不重载这三个方法叶子节点重载这三个方法结论 写在前面
数据结构模式 常常有一些组件在内部具有特定的数据结构。如何… 设计模式21-组合模式Composite Pattern 写在前面 动机定义与结构定义结构主要类及其关系 C代码推导优缺点应用场景总结补充叶子节点不重载这三个方法叶子节点重载这三个方法结论 写在前面
数据结构模式 常常有一些组件在内部具有特定的数据结构。如何让客户程序依赖这些特定的数据结构将极大的破坏组件的复用。那么这个时候将这些特定数据结构封装在内部。在外部提供统一的接口来实现与特定数据结构无关的访问。是一种行之有效的解决方案。
典型模式
组合模式迭代器模式 动机
软件在某些情况下客户代码过多的依赖于对象容器复杂的内部实现结构对象容器内部实现结构而非抽象接口的变化将引起客户代码的频繁变化。代码的维护性扩展性等弊端。那么如何将客户代码与复杂的对象容器结构进行解耦让对象容器自己来实现自身的复杂结构。而使得客户代码就像处理简单对象一样来实现处理复杂的对象容器在软件开发中有时我们需要处理树形结构的数据。例如图形编辑器中一个复杂图形可以由多个简单图形如线条、圆形、矩形等组合而成。无论是单个简单图形还是复杂图形的组合从操作上看它们应当被视为一个整体。组合模式的动机是通过将对象组合成树形结构来表示“部分-整体”的层次结构使得客户端可以一致地处理单个对象和组合对象。
定义与结构
定义
组合模式允许你将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性。
结构 这张图是一个UML统一建模语言类图用于展示软件系统中类之间的结构和关系。通过图形化的方式描述了类的属性、操作方法以及类之间的继承、关联等关系。
主要类及其关系 Client客户端 继承自Component类。表示一个使用组件的客户端实体。客户端通过继承Component类获得了对子节点的操作能力包括添加、删除和获取子节点。 Component组件 是一个抽象类代表了一个具有子组件概念的通用组件。它定义了三个操作方法 Add(Component): 添加一个子组件。Remove(Component): 移除一个子组件。GetChild(int): 根据索引获取子组件。 还有一个属性children用于存储子组件的集合尽管这个属性在图中没有明确标出但根据UML的惯例和类的操作可以推断出来。 Leaf叶子节点 继承自Component类。表示没有子节点的组件即树的叶子。它同样定义了操作列表但这里特别指出了一个forall g in children的操作这实际上是一个伪代码或注释因为叶子节点没有子节点children为空或不存在所以这个操作在叶子节点上下文中不适用。这里的展示可能只是为了强调Leaf类继承自Component类并保留了Component的接口结构。 Composite复合节点 继承自Component类。表示具有多个子组件的复合结构如树中的非叶子节点。它除了具有Component类定义的操作外还特别指出了对子节点g的操作g.Operation():这里g代表了一个子组件的实例这个注释或伪代码表明Composite类可以对其子节点执行某种操作但没有具体说明是什么操作这取决于实际的应用场景。
这张UML类图展示了一个典型的组合模式Composite Pattern的结构其中Component是一个抽象类代表了一个具有共同接口的对象这个接口允许在组件的单个对象和组合对象之间进行一致的操作。Client类展示了如何使用这个结构而Leaf和Composite类则分别代表了结构中的叶子节点和复合节点。通过这种方式系统可以以统一的方式处理单个对象和组合对象简化了客户端代码并提高了系统的可扩展性。
C代码推导
以下是一个使用组合模式的C代码示例模拟一个文件系统其中目录可以包含文件或其他子目录。
抽象组件类
#include iostream
#include vector
#include string// 抽象组件类表示文件系统的节点
class FileSystemComponent {
public:virtual void showDetails(int indent 0) const 0;virtual void add(FileSystemComponent* component) {throw std::runtime_error(Cannot add to a leaf component);}virtual void remove(FileSystemComponent* component) {throw std::runtime_error(Cannot remove from a leaf component);}virtual ~FileSystemComponent() default;
};叶子节点类文件
class File : public FileSystemComponent {
private:std::string name;public:File(const std::string name) : name(name) {}void showDetails(int indent 0) const override {std::cout std::string(indent, ) name std::endl;}
};组合节点类目录
class Directory : public FileSystemComponent {
private:std::string name;std::vectorFileSystemComponent* components;public:Directory(const std::string name) : name(name) {}void add(FileSystemComponent* component) override {components.push_back(component);}void remove(FileSystemComponent* component) override {components.erase(std::remove(components.begin(), components.end(), component), components.end());}void showDetails(int indent 0) const override {std::cout std::string(indent, ) name / std::endl;for (const auto component : components) {component-showDetails(indent 2);}}~Directory() {for (auto component : components) {delete component;}}
};客户端代码
int main() {FileSystemComponent* rootDir new Directory(root);FileSystemComponent* homeDir new Directory(home);FileSystemComponent* userDir new Directory(user);FileSystemComponent* file1 new File(file1.txt);FileSystemComponent* file2 new File(file2.txt);FileSystemComponent* file3 new File(file3.txt);rootDir-add(homeDir);homeDir-add(userDir);userDir-add(file1);userDir-add(file2);homeDir-add(file3);rootDir-showDetails();delete rootDir;return 0;
}运行结果
root/home/user/file1.txtfile2.txtfile3.txt优缺点
优点
统一性组合模式使得客户端可以一致地处理单个对象和组合对象统一了对叶子节点和组合节点的操作。灵活性可以很方便地增加新的节点类型如新的文件类型或目录类型符合开闭原则。简化客户端代码客户端无需关心处理的是单个对象还是组合对象减少了代码复杂性。
缺点
复杂性可能会导致系统中类的数量增加特别是当需要支持复杂的树形结构时。难以限制组合在组合模式中很难限制哪些组件可以组合在一起容易导致不合理的组合结构。
应用场景
组合模式在以下场景中应用较多
需要表示树形结构的场景如文件系统、组织结构、UI组件树等。需要统一处理单个对象和组合对象的场景如图形编辑器中的简单图形和组合图形。需要动态构建部分-整体结构的场景如菜单和子菜单的构建产品配置和子组件的构建。
总结
组合模式通过将对象组合成树形结构来表示“部分-整体”的层次结构使得客户端可以一致地处理单个对象和组合对象。它在需要处理树形结构的数据时非常有效能够简化客户端代码并提供很好的扩展性。然而由于可能引入更多的类特别是当系统的组合结构复杂时需要注意管理组合的复杂性。组合模式采用树形结构来实现普遍存在的对象容器从而将一对多的关系转化为一对一的关系。使得客户代码可以一致的复用处理对象和和对象容器。无需关心处女的是单个对象还是组合的对象容器将客户代码与复杂的对象容器结构解耦是组合模式的核心思想。解耦之后客户代码将与纯粹的抽象接口而非对象容器的内部时间结构发生依赖从而更能应对变化。组合模式在具体的实现中可以让父对象中的子对象反向追溯。如果富对线有频繁的便利需求可以使用缓存技巧来改善效率。
补充
在组合模式中叶子节点通常不需要实现即重载Add(Component), Remove(Component), GetChild(int)这三个方法因为叶子节点不包含子节点。这些方法主要用于组合节点Composite以便管理子节点。但有时为了简化代码或提高灵活性叶子节点也可能会实现这些方法。以下是叶子节点重载与不重载这三个方法的优缺点对比。
叶子节点不重载这三个方法
实现方式
在叶子节点中这些方法通常被声明但不实现在C中通常可以抛出异常或者是空实现。叶子节点不需要管理子组件。
class Leaf : public Component {
public:void Add(Component* component) override {throw std::runtime_error(Leaf nodes do not support Add operation);}void Remove(Component* component) override {throw std::runtime_error(Leaf nodes do not support Remove operation);}Component* GetChild(int index) override {throw std::runtime_error(Leaf nodes do not support GetChild operation);}void Operation() override {// 具体叶子节点的操作实现}
};优点
清晰的语义叶子节点明确不支持子节点管理操作这使得代码的意图更加清晰避免了误用。更强的类型安全由于明确抛出异常或不实现可以在运行时捕捉到错误而不是让无意义的操作通过。符合职责分离原则叶子节点只专注于具体操作不需要处理与子节点相关的逻辑。
缺点
客户端代码需要做额外的检查客户端需要知道一个组件是否是叶子节点以避免调用不支持的方法可能增加了客户端的复杂性。减少了一致性对客户端来说调用这些方法会抛出异常或导致错误这可能会影响代码的一致性和简洁性。
叶子节点重载这三个方法
实现方式
叶子节点实现重载这些方法但不执行任何操作或返回特定值如nullptr。
class Leaf : public Component {
public:void Add(Component* component) override {// 叶子节点不支持添加操作但实现了这个方法}void Remove(Component* component) override {// 叶子节点不支持移除操作但实现了这个方法}Component* GetChild(int index) override {return nullptr; // 叶子节点没有子节点返回空指针}void Operation() override {// 具体叶子节点的操作实现}
};优点
简化客户端代码客户端代码不需要检查节点类型可以统一调用Add, Remove, GetChild简化了代码逻辑。提高一致性所有组件叶子节点和组合节点都实现了相同的接口提供了一致的编程接口。增加灵活性在未来扩展时如果叶子节点需要支持子节点管理可以直接扩展已有方法。
缺点
隐藏潜在错误叶子节点实现了不应该执行的操作如Add和Remove可能导致误用而不易发现。不符合职责分离原则叶子节点本不应该涉及子节点管理操作实现这些方法可能违反单一职责原则。占用资源虽然通常影响很小但实现这些无操作的方法也会占用一些资源例如代码空间特别是在资源受限的环境中。
结论 不重载方法的情况适用于严格遵循职责分离原则的场景。通过不重载方法明确区分了叶子节点和组合节点的职责使得代码更清晰类型安全性更高。这种方式适合对系统稳定性和安全性要求较高的场合或在需要明确捕获误用场景的应用中使用。 重载方法的情况适用于追求客户端代码简单性和一致性的场景。通过重载这些方法客户端不需要区分叶子节点和组合节点统一处理所有组件减少了代码的复杂性。这种方式适合在系统中灵活性要求较高、且不易出错的场合。
综上选择是否重载这些方法取决于具体应用的需求、开发团队的编码习惯和系统的复杂性。如果系统需要严格的职责区分和类型安全性建议不重载这些方法如果系统追求统一性和简洁性可以考虑重载这些方法。