设计模式之组合模式(C++)

简介: 设计模式之组合模式(C++)

一、组合模式是什么?

      组合模式是一种结构型的软件设计模式,将对象组合成树形结构,以凸显“部分-整体”的层次结构,使客户端对单个对象和组合对象的操作具备一致性。


      组合模式和桥接模式都应用了组合的思想,不同之处在于:桥接模式侧重于同级别间的组合,如多个属性的组合,避免了类爆炸;组合模式侧重于部分和整体的组合,避免了单对象和组合对象的区别对待,那样会增加程序复杂度。


      组合模式的优点:


  1. 层次鲜明。凸显“部分-整体”的层次结构。
  2. 一致性。对叶子对象(单)和容器对象(组合)的操作具备良好一致性。
  3. 节点自由度高。在结构中按需自由添加节点。

      组合模式的缺点:


  1. 设计更抽象。
  2. 应用场景限制。

      组合模式一般分为透明式组合模式和安全式组合模式。


1)透明式组合模式:将公共接口封装到抽象节点中,所有节点具备一致行为。

2)安全式组合模式:各层次差异较大,使用不同操作时建议采用该模式。

二、透明式组合模式

2.1 结构图

      客户端即Main主函数,采用透明式,无需分辨节点是叶子还是容器,只需使用抽象节点。

2.2 代码示例

      场景描述:我要设计一个文件系统,在文件夹中可以添加删除文件或者文件夹。

//File.h
/****************************************************/
#pragma once
#include <iostream>
#include <list>
using namespace std;
// 抽象类-节点
class Node
{
public:
  // 构造函数
  explicit Node(string name) :m_name(name) {};
  // 析构函数
  virtual ~Node() {};
  // 添加
  virtual void add(Node *node) {};
  // 删除
  virtual void remove(Node *node) {};
  // 显示
  virtual void show(int space) {
    for (int i = 0; i < space; i++) {
      cout << "  ";
    }
    cout << m_name << endl;
  }
protected:
  string m_name;                                       // 名字
};
// 具体类-Word文件
class WordFile :public Node
{
public:
  // 构造函数
  explicit WordFile(string name) :Node(name) {};
  // 析构函数
  virtual ~WordFile() {};
};
// 具体类-文件夹
class Folder :public Node
{
public:
  // 构造函数
  explicit Folder(string name) :Node(name) {};
  // 析构函数
  virtual ~Folder() {
    nodeList.clear();
  }
  // 添加
  virtual void add(Node *node) {
    nodeList.emplace_back(node);
  }
  // 删除
  virtual void remove(Node *node) {
    nodeList.remove(node);
  }
  // 显示
  virtual void show(int space) {
    Node::show(space);
    space++;
    for (auto node : nodeList) {
      node->show(space);
    }
  }
private:
  list<Node*> nodeList;                                // 节点列表
};
//main.cpp
/****************************************************/
#include <iostream>
#include <string>
#include "File.h"
using namespace std;
int main()
{
  Node *f0 = new Folder("我的文件夹");
  // 文件夹1中放入Word2和Word3,并将文件夹1放入我的文件夹
  Node *f1 = new Folder("文件夹1");
  Node *w2 = new WordFile("Word2");
  Node *w3 = new WordFile("Word3");
  f1->add(w2);
  f1->add(w3);
  f0->add(f1);
  // 将Word1放入我的文件夹
  Node *w1 = new WordFile("Word1");
  f0->add(w1);
  // 显示我的文件夹中的内容
  f0->show(0);
  // 删除文件夹1中的Word2文件,再次显示我的文件夹中的内容
  f1->remove(w2);
  f0->show(0);
  // 删除指针并置空
  delete f0, f1, w1, w2, w3;
  f0 = nullptr;
  f1 = nullptr;
  w1 = nullptr;
  w2 = nullptr;
  w3 = nullptr;
  return 0;
}

程序结果如下。

      在上述示例中,我采用的都是抽象节点,因为抽象类中涵盖了几乎所有的行为。对外界(客户端)而言,叶子和容器的行为没有区别,它们的区别是不可见的也是透明的。但同样,这样的设计一定意义上也是不安全的,因为叶子不可能和容器有完全一致的行为,就像上文中文件夹的增删功能,文件是没有的,如果对文件进行文件夹的相关独特操作,要做相关的异常判断。


      该模式是组合模式中常用的,虽然它违背了设计模式中的接口隔离原则(只提供需要的接口,不需要的接口要屏蔽)。

三、安全式组合模式

3.1 结构图

      客户端即Main主函数,采用安全式,抽象类中只规定基础操作,而叶子和容器各自独有的操作将放在自身中完成,因此客户端在调用时无法直接使用抽象节点。

3.2 代码示例

      场景描述:我要设计一个文件系统,在文件夹中可以添加删除文件或者文件夹。

//File.h
/****************************************************/
#pragma once
#include <iostream>
#include <list>
using namespace std;
// 抽象类-节点
class Node
{
public:
  // 构造函数
  explicit Node(string name) :m_name(name) {};
  // 析构函数
  virtual ~Node() {};
  // 显示
  virtual void show(int space) = 0;
protected:
  string m_name;                                       // 名字
};
// 具体类-Word文件
class WordFile :public Node
{
public:
  // 构造函数
  explicit WordFile(string name) :Node(name) {};
  // 析构函数
  virtual ~WordFile() {};
  // 显示
  virtual void show(int space) {
    for (int i = 0; i < space; i++) {
      cout << "  ";
    }
    cout << m_name << endl;
  }
};
// 具体类-文件夹
class Folder :public Node
{
public:
  // 构造函数
  explicit Folder(string name) :Node(name) {};
  // 析构函数
  virtual ~Folder() {
    nodeList.clear();
  }
  // 添加
  void add(Node *node) {
    nodeList.emplace_back(node);
  }
  // 删除
  void remove(Node *node) {
    nodeList.remove(node);
  }
  // 显示
  virtual void show(int space) {
    for (int i = 0; i < space; i++) {
      cout << "  ";
    }
    cout << m_name << endl;
    space++;
    for (auto node : nodeList) {
      node->show(space);
    }
  }
private:
  list<Node*> nodeList;                                // 节点列表
};
//main.cpp
/****************************************************/
#include <iostream>
#include <string>
#include "File.h"
using namespace std;
int main()
{
  Folder *f0 = new Folder("我的文件夹");
  // 文件夹1中放入Word2和Word3,并将文件夹1放入我的文件夹
  Folder *f1 = new Folder("文件夹1");
  WordFile *w2 = new WordFile("Word2");
  WordFile *w3 = new WordFile("Word3");
  f1->add(w2);
  f1->add(w3);
  f0->add(f1);
  // 将Word1放入我的文件夹
  WordFile *w1 = new WordFile("Word1");
  f0->add(w1);
  // 显示我的文件夹中的内容
  f0->show(0);
  // 删除文件夹1中的Word2文件,再次显示我的文件夹中的内容
  f1->remove(w2);
  f0->show(0);
  // 删除指针并置空
  delete f0, f1, w1, w2, w3;
  f0 = nullptr;
  f1 = nullptr;
  w1 = nullptr;
  w2 = nullptr;
  w3 = nullptr;
  return 0;
}

     程序结果如下。

      在上述示例中,客户端无法直接使用抽象节点了,因为抽象节点中没有add和remove的操作,但这样更安全了,叶子和容器不会调用对方的功能进而触发异常。

      注意:该模式违背了依赖倒置原则(程序设计要依赖于抽象接口,不要依赖于具体实现)。

四、总结

      我尽可能用较通俗的话语和直观的代码例程,来表述我对组合模式的理解,或许有考虑不周到的地方,如果你有不同看法欢迎评论区交流!希望我举的例子能帮助你更好地理解组合模式。

      如果文章帮助到你了,可以点个赞让我知道,我会很快乐~加油!

相关文章
|
2月前
|
设计模式 安全 测试技术
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
【C/C++ 设计模式 单例】单例模式的选择策略:何时使用,何时避免
66 0
|
2月前
|
设计模式 算法 C++
【C++ 泛型编程 进阶篇】C++元模板编程与设计模式的结合应用教程(二)
【C++ 泛型编程 进阶篇】C++元模板编程与设计模式的结合应用教程
30 0
|
2月前
|
设计模式 存储 uml
C++ 设计模式实战:外观模式和访问者模式的结合使用,派生类访问基类的私有子系统
C++ 设计模式实战:外观模式和访问者模式的结合使用,派生类访问基类的私有子系统
30 1
|
2月前
|
设计模式 Java uml
C++设计模式之 依赖注入模式探索
C++设计模式之 依赖注入模式探索
56 0
|
17天前
|
设计模式 安全 Java
[设计模式Java实现附plantuml源码~结构型]树形结构的处理——组合模式
[设计模式Java实现附plantuml源码~结构型]树形结构的处理——组合模式
|
17天前
|
设计模式 存储 Java
C++从入门到精通:3.5设计模式——提升代码可维护性与可扩展性的关键
C++从入门到精通:3.5设计模式——提升代码可维护性与可扩展性的关键
|
20天前
|
设计模式 Java 容器
【设计模式系列笔记】组合模式
组合模式(Composite Pattern)是一种结构型设计模式,它允许将对象组合成树状结构以表示部分-整体的层次结构。组合模式使得客户端可以统一处理单个对象和对象组合,而无需区分它们的类型。
43 12
|
29天前
|
设计模式 Java
小谈设计模式(20)—组合模式
小谈设计模式(20)—组合模式
|
1月前
|
设计模式 存储 Java
23种设计模式,组合模式的概念优缺点以及JAVA代码举例
【4月更文挑战第5天】组合模式(Composite Pattern)是一种结构型设计模式,旨在通过将对象组合成树形结构以表示部分-整体的层次结构,使用户对单个对象和组合对象的使用具有一致性。这种模式让客户可以统一地处理单个对象和组合对象
40 6
|
2月前
|
设计模式 算法 中间件
【C++ 可调用对象的应用】C++设计模式与现代编程技巧:深入可调用对象的世界
【C++ 可调用对象的应用】C++设计模式与现代编程技巧:深入可调用对象的世界
126 1