设计模式-组合模式

30 阅读4分钟

# 组合模式

1.简介

组合模式是一种结构型设计模式,你可以使用它将对象组合成树状结构,并且像使用独立对象一样使用它们。

假设我们现在有具体的产品和包装盒子。它们之间的关系是一个盒子中可以包含多个产品,或者几个小盒子。每个小盒子也是同样的道理。每个产品都有自己的价格,如果现在让你来计算它们的总价格呢?

你可以选择直接计算,打开所有的盒子,取出所有的商品,然后计算总价。但在代码层面中你需要提前知道所有产品和盒子的类别,最多嵌套几层等相应的规则,全部了解清楚之后你可能才可以编写与之相对应的代码。

其实这种结构就像一颗倒过来的树,非常适合使用组合模式。因为你要以一种统一的方式操作单个对象和由这些对象组成的组合对象。

组合模式通常建议使用一个通用接口来与 产品盒子进行交互,并在该接口中声明一个计算总价的方法。

对于一个产品来说,该方法直接返回价格;对于一个盒子来说,该方法遍历盒子中所有的项目,计算每一个项目的价格,然后返回总价格。如果其中有一个小盒子,那么对小盒子也会进行一遍计算,直到计算出所有价格。你也可以在盒子的方法中增加额外费用,来作为该盒子的包装费。

这样的优点就在于 我们无需了解构成树状结构的对象的具体类, 只需要调用接口以相同的形式进行处理,当你调用该方法后,对象会将请求沿着树结构传递下去

2.UML图

组合模式1.png

  • 组件(Component):接口描述了树中简单项目和复杂项目共有的操作。
  • 叶节点(Leaf):是树的基本结构,他一般不包含子项目。一般情况下,叶节点最终会完成大部分的实际工作,因为它无法将工作指派给其他部分。
  • 容器(Container)/组合(Composite):是包含叶节点或其他容器子项目的单位。容器不知道其子项目所属的具体类,它只通过通用的组件接口与其子项目交互。 容器收到请求后会将工作分配给自己的子项目,处理中间结果,然后吧最终结果返回给客户端。
  • 客户端(Client):通过组件接口与所有项目交互,通过这种方式,客户端能以相同的方式与树状结构中的简单或者复杂项目交互。

​ 这里有两种实现方式:

​ 一种是安全式:如果是安全的那么就不会发生误操作的可能,能访问的方法就都是支持的。但是对于安全式而言,你就需要区分对象是容器对象还是叶节点对象,有时候就会需要对对象进行类型转换,必然是不够安全的。

​ 一种是透明式:从客户端的角度来看,不需要区分是容器对象还是叶节点对象,对于客户端而言都是 Component 组件对象,客户端无需关心,统一操作都是在组件对象中定义好的,所有继承它的节点都需要实现。然而有些操作对于叶子结点来说可能是不支持的,例如添加或移除节点,这是属于容器对象的操作。这就要求叶子结点也需要在代码层面上合理的处理好这些方法。

​ 对于组合模式而言在安全性和透明性上会更看重透明性,毕竟组合模式的目的是让客户端不需要区分操作的是什么对象,而是以一种统一的方式来进行操作

3.代码示例

现在我们来编写一个关于公司组织架构计算管理的示例:

1、设置一个通用接口(Component)

package com.gs.designmodel.combination;

/**
 * @author: Gaos
 * @Date: 2023-08-11 09:48
 *
 * 设置一个个体与组合通用的接口
 * 用来定义对外展示的统一处理接口
 **/
public abstract class OrganizationComponent {

    private String name;

    public OrganizationComponent(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public abstract void  add(OrganizationComponent organization);

    public abstract OrganizationComponent getChild(String orgName);

    public abstract int getStaffCount();

    @Override
    public String toString() {
        return name;
    }
}

2、容器对象

package com.gs.designmodel.combination;

import java.util.ArrayList;
import java.util.List;

/**
 * @author: Gaos
 * @Date: 2023-08-11 09:51
 *
 * 组合类
 * 此类持有一个List<OrganizationComponent> 并继承OrganizationComponent
 **/
public class OrganizationComposite extends OrganizationComponent{

    private List<OrganizationComponent> organizations = new ArrayList<>();

    public OrganizationComposite(String name) {
        super(name);
    }



    @Override
    public void add(OrganizationComponent organization) {
        organizations.add(organization);
    }

    @Override
    public OrganizationComponent getChild(String orgName) {
        for (OrganizationComponent item : organizations) {
            OrganizationComponent child = item.getChild(orgName);
            if(child != null) {
                return child;
            }
        }
        return null;
    }

    @Override
    public int getStaffCount() {
        return organizations.stream().mapToInt(OrganizationComponent::getStaffCount).sum();
    }
}

3、叶子结点

package com.gs.designmodel.combination;

/**
 * @author: Gaos
 * @Date: 2023-08-11 09:58
 *
 * 叶子节点
 * 也就是单个对象,需要注意的是我们要合理处理那些叶子节点不支持的接口方法
 **/
public class ItDepartment extends OrganizationComponent{
    public ItDepartment(String name) {
        super(name);
    }

    @Override
    public void add(OrganizationComponent organization) {
        throw new RuntimeException(this.getName() + "已是最下级部门,无法增加下属部门");
    }

    @Override
    public OrganizationComponent getChild(String orgName) {
        return getName().equals(orgName) ? this : null;
    }

    @Override
    public int getStaffCount() {
        return 20;
    }
}
package com.gs.designmodel.combination;

/**
 * @author: Gaos
 * @Date: 2023-08-11 10:08
 **/
public class HrDepartment extends OrganizationComponent{
    public HrDepartment(String name) {
        super(name);
    }

    @Override
    public void add(OrganizationComponent organization) {
        throw new RuntimeException(this.getName() + "已是最下级部门,无法增加下属部门");
    }

    @Override
    public OrganizationComponent getChild(String orgName) {
        return getName().equals(orgName) ? this : null;
    }

    @Override
    public int getStaffCount() {
        return 5;
    }
}

4、测试类

我们的目的是以统一的接口操作单个对象与其组合对象。

我们模拟一家公司,公司下有IT部门、行政部,还有一个分公司。分公司下也有一个IT部门、行政部,然后我们计算这个总公司的所有人数。

package com.gs.designmodel.combination;

/**
 * @author: Gaos
 * @Date: 2023-08-11 10:01
 **/
public class Test {


    public static void main(String[] args) {
        // 总部
        OrganizationComposite head = new OrganizationComposite("总公司");
        HrDepartment headHr = new HrDepartment("总公司行政部");
        ItDepartment headIt = new ItDepartment("总公司IT部");
        head.add(headIt);
        head.add(headHr);

        // 分公司
        OrganizationComposite branch = new OrganizationComposite("天津分公司");
        HrDepartment branchHr = new HrDepartment("天津分公司行政部");
        ItDepartment branchIt = new ItDepartment("天津分公司IT部");
        branch.add(branchIt);
        branch.add(branchHr);

        // 将分公司加到总部
        head.add(branch);

        System.out.println(head.getName() + "共有" + head.getStaffCount() + "名员工");

        OrganizationComponent item = head.getChild("天津分公司行政部");
        System.out.println(item.getName() + "共有" + item.getStaffCount() + "名员工");
    }
}

结果

总公司共有50名员工
天津分公司行政部共有5名员工

4、总结

当你需要实现树状对象结构时、当你希望客户端代码以相同方式处理简单和复杂元素时,你可以使用组合模式。

有很多模式的接口非常相似(例如桥接模式、状态模式、策略模式、适配器模式等),实际上它们都基于组合模式---即将工作委派给其他对象,也各自解决了不同的问题。

对于功能差异比较大的类,抽象出公共接口毕竟困难,需要经验充足,甚至处理一般化组件接口,使维护人员难以理解。

参考文章:

refactoringguru.cn/design-patt…

blog.csdn.net/ShuSheng000…

希望这篇文章对大家有所帮助,您的赞和收藏是对我最大的支持和认可!