设计模式系列:搞懂组合模式,单对象与组合对象对外统一接口

组合模式的定义:又叫作整体-部分(Part-Whole)模式,通过将单个对象(叶节点)和组合对象用相同的接口表示,使客户端对单个对象和组合对象的访问具有一致性。它是一种将对象组合成树状的层次结构的模式。属于结构型设计模式。

组合模式一般用来描述整体与部分的关系,它将对象组合到树状结构,顶层的节点被称为根节点,最末级的节点成为叶节点,中间的节点成为树枝节点,树形结构如下图。

        由上图可以看出,根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶节点与树枝节点在语义上不属于同一种类型,但是在组合模式中,我们会把树枝节点和叶节点看作同一种数据类型(用统一接口定义),让它们具备一致行为。这样,在组合模式中,整个树形结构中的对象都属于同一种类型,客户端不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给客户端的使用带来极大的便利。

组合模式的结构:组合模式包含以下3个角色。

  1. 抽象根节点(Component):为叶节点和树枝节点声明公共接口,并实现它们的默认行为。
  2. 树叶节点(Leaf):组合中的叶节点对象,它没有子节点,是树状结构的最末级。
  3. 树枝节点(Composite):组合中的分支节点对象,它有子节点,主要作用是存储和管理子节点。

组合模式的实现:组合模式分为透明式组合模式和安全式组合模式。我们以公司组织架构为例,分别用透明式组合模式和安全式组合模式实现。

某公司组织架构如下图,公司包括销售部、研发部和财务部,销售部和财务部下又分为A组和B组。

透明组合模式的实现:

        在透明组合模式中,抽象根节点声明了所有子类中的全部方法,客户端无须区别叶节点和树枝节点,它们具备一致的接口,对客户端来说是透明的。但其缺点是:叶节点本来没有增加 Add()、删除Remove() 及 获取子节点GetChild() 的方法,却要实现它们(空实现或抛出异常),这样会带来一些安全性问题。

//抽象根节点
public interface DeptComponent {
    void add(DeptComponent deptComponent);

    void remove(DeptComponent deptComponent);

    List<DeptComponent> getChildren();

    void getName();
}

//叶节点
public class LeafDept implements DeptComponent {
    private String name;

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

    @Override
    public void add(DeptComponent deptComponent) { }

    @Override
    public void remove(DeptComponent deptComponent) { }

    @Override
    public List<DeptComponent> getChildren() {
        return null;
    }

    @Override
    public void getName() {
        System.out.println(name+"前来报到!");
    }
}

//树枝节点
public class CompositeDept implements DeptComponent{
    private List<DeptComponent> children = new ArrayList<>();
    private String name;

    CompositeDept(String name){
        this.name = name;
    }

    @Override
    public void add(DeptComponent deptComponent) {
        children.add(deptComponent);
    }

    @Override
    public void remove(DeptComponent deptComponent) {
        children.remove(deptComponent);
    }

    @Override
    public List<DeptComponent> getChildren() {
        return children;
    }

    @Override
    public void getName() {
        System.out.println(name+"前来报到!");
    }
}

//测试类
public class CompositeTest {
    public static void main(String[] args) {
        DeptComponent company = new CompositeDept("某公司");
        DeptComponent saleDept = new CompositeDept("销售部");
        DeptComponent developmentDept = new CompositeDept("研发部");
        DeptComponent financeDept = new CompositeDept("财务部");

        DeptComponent saleA = new LeafDept("销售部A组");
        DeptComponent saleB = new LeafDept("销售部B组");
        DeptComponent developmentA = new LeafDept("研发部A组");
        DeptComponent developmentB = new LeafDept("研发部B组");

        developmentDept.add(developmentA);
        developmentDept.add(developmentB);
        saleDept.add(saleA);
        saleDept.add(saleB);

        company.add(saleDept);
        company.add(developmentDept);
        company.add(financeDept);

        List<DeptComponent> children = company.getChildren();
        children.stream().forEach(deptComponent -> {
            deptComponent.getName();
            deptComponent.getChildren().stream().forEach(deptComponent1 -> deptComponent1.getName());
        });
    }
}

透明组合模式的结构图:

安全组合模式的实现:

        在安全组合模式中,将管理叶节点的方法移到树枝节点中,抽象根节点和叶节点没有对子对象的管理方法,避免了透明组合模式的安全性问题,但由于叶节点和树枝节点有不同的接口,客户端在调用时要知道叶节点和树枝节点的存在,所以失去了透明性。

//抽象根节点
public interface DeptComponent {
    void getName();
}

//叶节点
public class LeafDept implements DeptComponent {
    private String name;

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

    @Override
    public void getName() {
        System.out.println(name+"前来报到!");
    }
}

//树枝节点
public class CompositeDept implements DeptComponent{
    private List<DeptComponent> children = new ArrayList<>();
    private String name;

    CompositeDept(String name){
        this.name = name;
    }

    public void add(DeptComponent deptComponent) {
        children.add(deptComponent);
    }

    public void remove(DeptComponent deptComponent) {
        children.remove(deptComponent);
    }

    public List<DeptComponent> getChildren() {
        return children;
    }

    @Override
    public void getName() {
        System.out.println(name+"前来报到!");
    }
}

//测试类
public class CompositeTest {
    public static void main(String[] args) {
        CompositeDept company = new CompositeDept("某公司");
        CompositeDept saleDept = new CompositeDept("销售部");
        CompositeDept developmentDept = new CompositeDept("研发部");
        CompositeDept financeDept = new CompositeDept("财务部");

        DeptComponent saleA = new LeafDept("销售部A组");
        DeptComponent saleB = new LeafDept("销售部B组");
        DeptComponent developmentA = new LeafDept("研发部A组");
        DeptComponent developmentB = new LeafDept("研发部B组");

        developmentDept.add(developmentA);
        developmentDept.add(developmentB);
        saleDept.add(saleA);
        saleDept.add(saleB);

        company.add(saleDept);
        company.add(developmentDept);
        company.add(financeDept);

        List<DeptComponent> children = company.getChildren();
        children.stream().forEach(deptComponent -> {
            deptComponent.getName();
            if(deptComponent instanceof CompositeDept){
                CompositeDept compositeDept = (CompositeDept) deptComponent;
                compositeDept.getChildren().stream().forEach(deptComponent1 -> deptComponent1.getName());
            }
        });
    }
}

安全组合模式的结构图:

组合模式的优点:

  1. 组合模式使客户端处理单个对象和组合对象逻辑一致,这简化了客户端代码;
  2. 更容易在组合体内加入新的对象,满足“开闭原则”;

组合模式的缺点:

  1. 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
  2. 不容易限制容器中的构件;
  3. 不容易用继承的方法来增加构件的新功能;

组合模式的使用场景:

  1. 在需要表示一个对象整体与部分的层次结构的场合。
  2. 要求对客户端隐藏组合对象与单个对象的差异,使客户端可以用统一的接口使用组合结构中的所有对象的场合。
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 13
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风雨编码路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值