找回密码
 立即注册
首页 业界区 安全 [设计模式/Java] 设计模式之组合模式(部分-整体模式)【1 ...

[设计模式/Java] 设计模式之组合模式(部分-整体模式)【19】

鞍汉 2025-6-1 00:04:33
概述: 组合模式(Composite Pattern):= 部分-整体模式(Part-Whole Pattern)

场景引入

场景1:文件系统的目录层次结构

1.png


  • 我们对于这个图片肯定会非常熟悉,这两幅图片我们都可以看做是一个文件结构,对于这样的结构我们称之为树形结构
  • 数据结构中我们了解到可以通过调用某个方法来遍历整个树,当我们找到某个叶子节点的时候,就可以对叶子节点进行相关的操作。
    我们可以将这颗树理解成一个大的容器容器里面包含很多的成员对象,这些对象即可以是容器对象也可以是叶子对象
  • 但是由于容器对象叶子对象在功能上的区别,使得我们在使用的过程中【必须要区分】容器对象叶子对象,但是这样就会给客户带来不必要的麻烦。
作为客户,它希望能够一致地对待容器对象和叶子对象
这就是组合模式的设计动机:组合模式定义了如何将【容器对象】和【叶子对象】进行【递归组合】,使得客户在使用的过程中无需区分,可以对它们进行一致的处理
场景2:部分与整体的关系(电脑与零部件)


  • 电脑与其零件也是部分-整体关系
模式定义


  • 组合模式(Composite Pattern) 也称为 整体-部分(Part-Whole)模式

    • 它的宗旨是通过将单个对象(叶子节点)组合对象(树枝节点)用相同的接口进行表示,使得客户对单个对象和组合对象的使用具有一致性
    • 用于把一组【相似的对象】当做一个【单一的对象】
    • 组合模式依据树形结构来组合对象,用来表示部分以及整体层次

  • 组合模式

    • 一般用来描述 整体部分 的关系,它将对象组织到树形结构中:

      • 最顶层的节点称为 根节点
      • 根节点下面可以包含 树枝节点叶子节点
      • 树枝节点下面又可以包含 树枝节点叶子节点

    • 组合模式对单个对象(叶子对象)组合对象(组合对象)具有一致性,它将对象组合到树结构中,可以用来描述整体和部分的关系
    • 同时,它也模糊了简单元素(叶子对象)复杂元素(容器对象)的概念,使得客户能够像处理简单元素一样来处理复杂元素,从而使得客户程序能够与复杂元素的内部结构解耦

  • 模式的分类。

    • 这种类型的设计模式属于结构型模式,它创建了对象组的树形结构
    • 这种模式创建了一个包含自己对象组的类。该类提供了修改相同对象组的方式。

2.png

由上图可见:


  • 其实根节点树枝节点本质上是同一种数据类型(蓝色圆圈),可以作为容器使用;
  • 叶子节点树枝节点在语义上不属于同一种类型,但是在组合模式 中,会把树枝节点叶子节点认为是同一种数据类型(用同一接口定义),让它们具备一致行为
  • 这样,在组合模式中,整个树形结构中的对象都是同一种类型,带来的一个好处就是客户无需辨别 树枝节点 还是 叶子节点,而是可以直接进行操作,给客户使用带来极大的便利。
比如说,计算机的文件系统,文件系统由文件和目录组成,目录下面也可以包含文件或者目录,计算机的文件系统是用递归结构来进行组织的,对于这样的数据结构是非常适用使用组合模式的。


  • 组合模式的核心:借助同一接口,使叶子节点和树枝节点的操作具备一致性

    • 在使用组合模式中需要注意一点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。
    • 这就是组合模式能够将叶子节点对象节点进行一致处理的原因。

模式的组成

3.png

组合模式角色:


  • 抽象根节点(Component)

    • 作为组合中对象声明接口,实现所有类共有接口的默认行为

  • 树枝节点(Composite)

    • 定义有子部件的组合部件行为,存储子部件(组合树枝节点和叶子节点形成一个树形结构;)
    • 在Component接口中实现与子部件有关的操作。

  • 叶子节点(Leaf)

    • 叶子节点对象,其下再无分支,是系统层次遍历的最小单位;

  • 客户端(Client):

    • 使用 Component 部件的对象。

透明组合模式


  • 透明模式:把组合(树节点)使用的方法放到统一行为(Component)中,让不同层次(树节点,叶子节点)的结构都具备一致行为
其 UML 类图如下所示:
4.png

透明组合模式: 把所有公共方法都定义在 Component 中:


  • 好处:


  • 客户端无需分辨是叶子节点(Leaf)树枝节点(Composite),它们具备完全一致的接口


  • 缺点:


  • 叶子节点(Leaf)会继承得到一些它所不需要(管理子类操作的方法)的方法,这与设计模式 接口隔离原则 相违背。
解决方法:透明组合模式 中,由于 Component 包含叶子节点所不需要的方法。因此,我们直接将这些方法默认抛出UnsupportedOperationException异常。
安全组合模式


  • 安全组合模式:统一行为(Component)只规定系统各个层次的最基础的一致行为,而把组合(树节点)本身的方法(管理子类对象的添加,删除等)放到自身当中
其 UML 类图如下所示:
5.png


  • 安全组合模式


  • 把系统各层次公有的行为定义在 Component 中,把组合(树节点)的特有行为(管理子类增加,删除等)放到自身(Composite)中。


  • 好处


  • 接口定义职责清晰,符合设计模式 单一职责原则 和 接口隔离原则;


  • 缺点


  • 客户需要区分树枝节点(Composite)和叶子节点(Leaf)
这样才能正确处理各个层次的操作,客户端无法依赖抽象(Component),违背了设计模式依赖倒置原则
案例实践

CASE 树形结构
  1. #include<iostream>
  2. #include  <memory>
  3. #include <list>
  4. using namespace std;
  5. class Componet;
  6. typedef shared_ptr<Componet> COMPONET;
  7. //Component 公共类
  8. class Componet {
  9. public:
  10.     explicit Componet(string & _name) : name(_name){}
  11.     virtual ~Componet() {  };
  12.     virtual void Add(COMPONET c) = 0;
  13.     virtual void Remove(COMPONET c) = 0;
  14.     virtual void Display(int depth){
  15.         for (int i = 0; i < depth; i++)
  16.             cout << "-";
  17.         cout << name << endl;
  18.     }
  19. protected:
  20.     std::string name;
  21. };
  22. //Leaf 叶子
  23. class Leaf : public Componet{
  24. public:
  25.     explicit Leaf(std::string _name) : Componet(_name){}
  26.     ~Leaf() override= default;;
  27.     void Remove(COMPONET c) override {
  28.         throw "Cannot remove from a leaf";
  29.     }
  30.     void Add(COMPONET c) override{
  31.         throw "Cannot add to a leaf";
  32.     }
  33. };
  34. //Composite 组合
  35. class Composite : public Componet{
  36.         public:
  37.                 Composite(string _name) : Componet(_name) {}
  38.                 ~Composite() {}
  39.                 void Add(COMPONET c) override {
  40.                         children.push_back(c);
  41.                 }
  42.                 void Remove(COMPONET c) override{
  43.                         children.remove(c);
  44.                 }
  45.                 void Display(int depth) override {
  46.                         Componet::Display(depth);
  47.                         for (const auto& itr : children)
  48.                         {
  49.                                 itr->Display(depth + 3);
  50.                         }
  51.                 }
  52.         private:
  53.                 std::list<COMPONET> children;
  54. };
  55. //main 方法
  56. int main(int argc,char **argv){
  57.         //建立根
  58.         COMPONET root(new Composite(std::string("root")));
  59.         //添加两片叶子到根
  60.         root->Add(COMPONET(new Leaf(std::string("Leaf A"))));
  61.         root->Add(COMPONET(new Leaf(std::string("Leaf B"))));
  62.         //创建分支
  63.         COMPONET compositeX(new Composite(string("composite X")));
  64.         compositeX->Add(COMPONET(new Leaf(std::string("Leaf A"))));
  65.         compositeX->Add(COMPONET(new Leaf(std::string("Leaf B"))));
  66.         //分支挂在根上
  67.         root->Add(compositeX);
  68.         //创建分支
  69.         COMPONET compositeY(new Composite(string("composite Y")));
  70.         compositeY->Add(COMPONET(new Leaf(std::string("Leaf A"))));
  71.         compositeY->Add(COMPONET(new Leaf(std::string("Leaf B"))));
  72.         //分支挂在另一个分支上
  73.         compositeX->Add(compositeY);
  74.         root->Add(COMPONET(new Leaf(std::string("Leaf C"))));
  75.         COMPONET leaf(new Leaf(string("Leaf D")));
  76.         root->Add(leaf);//添加叶子
  77.         root->Remove(leaf);//移除叶子
  78.         //显示树形结构
  79.         root->Display(1);
  80.         return 0;
  81. }
复制代码
out
  1. -root
  2. ----Leaf A
  3. ----Leaf B
  4. ----composite X
  5. -------Leaf A
  6. -------Leaf B
  7. -------composite Y
  8. ----------Leaf A
  9. ----------Leaf B
  10. ----Leaf C
复制代码
CASE 职工: 职工及下属职工


  • 场景描述


  • 我们有一个类 Employee,该类被当作组合模型类
  • CompositePatternDemo 类使用 Employee 类来添加部门层次结构,并打印所有员工。
6.png


  • step1 创建 Employee 类,该类带有 Employee 对象的列表。


  • Employee
  1. import java.util.ArrayList;
  2. import java.util.List;
  3. public class Employee {
  4.    private String name;
  5.    private String dept;
  6.    private int salary;
  7.    private List<Employee> subordinates;
  8.    //构造函数
  9.    public Employee(String name,String dept, int sal) {
  10.       this.name = name;
  11.       this.dept = dept;
  12.       this.salary = sal;
  13.       subordinates = new ArrayList<Employee>();
  14.    }
  15.    public void add(Employee e) {
  16.       subordinates.add(e);
  17.    }
  18.    public void remove(Employee e) {
  19.       subordinates.remove(e);
  20.    }
  21.    public List<Employee> getSubordinates(){
  22.      return subordinates;
  23.    }
  24.    public String toString(){
  25.       return ("Employee :[ Name : "+ name
  26.       +", dept : "+ dept + ", salary :"
  27.       + salary+" ]");
  28.    }   
  29. }
复制代码

  • step2 使用 Employee 类来创建和打印员工的层次结构。


  • CompositePatternDemo
  1. public class CompositePatternDemo {
  2.    public static void main(String[] args) {
  3.       Employee CEO = new Employee("John","CEO", 30000);
  4.       Employee headSales = new Employee("Robert","Head Sales", 20000);
  5.       Employee headMarketing = new Employee("Michel","Head Marketing", 20000);
  6.       Employee clerk1 = new Employee("Laura","Marketing", 10000);
  7.       Employee clerk2 = new Employee("Bob","Marketing", 10000);
  8.       Employee salesExecutive1 = new Employee("Richard","Sales", 10000);
  9.       Employee salesExecutive2 = new Employee("Rob","Sales", 10000);
  10.       CEO.add(headSales);
  11.       CEO.add(headMarketing);
  12.       headSales.add(salesExecutive1);
  13.       headSales.add(salesExecutive2);
  14.       headMarketing.add(clerk1);
  15.       headMarketing.add(clerk2);
  16.       //打印该组织的所有员工
  17.       System.out.println(CEO);
  18.       for (Employee headEmployee : CEO.getSubordinates()) {
  19.          System.out.println(headEmployee);
  20.          for (Employee employee : headEmployee.getSubordinates()) {
  21.             System.out.println(employee);
  22.          }
  23.       }        
  24.    }
  25. }
复制代码
out
  1. Employee :[ Name : John, dept : CEO, salary :30000 ]
  2. Employee :[ Name : Robert, dept : Head Sales, salary :20000 ]
  3. Employee :[ Name : Richard, dept : Sales, salary :10000 ]
  4. Employee :[ Name : Rob, dept : Sales, salary :10000 ]
  5. Employee :[ Name : Michel, dept : Head Marketing, salary :20000 ]
  6. Employee :[ Name : Laura, dept : Marketing, salary :10000 ]
  7. Employee :[ Name : Bob, dept : Marketing, salary :10000 ]
复制代码
CASE 电脑与零部件


  • 电脑型号A与各个零件,电脑型号B与各个零件
CASE 总公司、分/子公司、部分之间的关系


CASE 算术表达式


  • 算术表达式:构建一个由操作数、操作符和子表达式组成的树形结构。
CASE GUI: 容器组件与子组件


  • GUI组件:在Java的AWT和Swing库中,容器(如Panel)可以包含其他组件(如按钮和复选框)。
CASE 文件系统

8.png


  • 浏览文件。 在文件系统中,可能存在很多种格式的文件,如果图片,文本文件、视频文件等等,这些不同的格式文件的浏览方式都不同,同时对文件夹的浏览就是对文件夹中文件的浏览,但是对于客户而言都是浏览文件,两者之间不存在什么差别,现在可用组合模式来模拟浏览文件。
  • 复制文件:一个个复制,或者整个文件夹复制
  • 文本编辑:对单个字操作/整段文字操作
H FAQ

Q: 透明组合模式 和 安全组合模式 都有各自的优点和缺点,那么我们应该优先选择哪一种呢?


  • 答:既然 组合模式 会被分为两种实现,那么肯定是不同的场合某一种会更加适合
即: 具体情况具体分析


  • 透明组合模式公共接口封装到抽象根节点(Component)中,那么系统所有节点就具备一致行为,所以如果当系统绝大多数层次具备相同的公共行为时,采用 透明组合模式,也许会更好(代价:为剩下少数层次节点引入不需要的方法);
  • 而如果当系统各个层次差异性行为较多或者树节点层次相对稳定(健壮)时,采用安全组合模式
Y 推荐文献


  • 设计模式之总述 - 博客园/千千寰宇
X 参考文献


  • 设计模式:组合模式(Composite Pattern) - CSDN
  • 组合模式 - 菜鸟教程
    本文作者:        千千寰宇   
    本文链接:         https://www.cnblogs.com/johnnyzen   
    关于博文:评论和私信会在第一时间回复,或直接私信我。   
    版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA     许可协议。转载请注明出处!
    日常交流:大数据与软件开发-QQ交流群: 774386015        【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!   

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册