组合模式【Composite Pattern】,什么是组合模式?结构?优缺点?主要角色?组合模式应用场景?实现案例?

目录


设计模式专栏目录(点击进入…)



什么是组合模式?

组合模式(Composite Pattern)是一种结构型设计模式,它允许你将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式让客户端可以一致地对待单个对象和组合对象。


组合模式结构

Component
    ├── Leaf
    └── Composite
           ├── Leaf
           └── Composite

组合模式优缺点

优点

(1)清晰的层次结构

组合模式能够很好地表示对象之间的层次关系,适合处理树形结构,如文件系统、菜单等。

(2)统一操作

客户端可以统一处理叶子对象和组合对象,而不需要关心它们之间的区别。通过组合模式,客户端代码可以简单地使用同一个接口进行操作。

(3)增加新的构件简单

通过继承组合对象或叶子对象的类,可以很容易地扩展系统,而不会影响现有代码。

(4)符合开闭原则

由于组合模式能够通过新增类来扩展系统,而不是修改现有类,它符合开闭原则(对扩展开放,对修改关闭)。

(5)灵活性

组合模式使得你可以轻松地添加或删除组合对象或叶子对象,灵活管理复杂的对象结构。

缺点

(1)设计较复杂

在实现过程中,可能需要引入更多的类和对象,特别是当层次结构较为复杂时,设计和维护变得困难。

(2)不容易限制组件类型

组合模式使得组合对象和叶子对象都实现了同一个接口,因此有时无法严格控制哪些对象可以作为组合对象或叶子对象,容易导致不合理的使用。

(3)性能开销

由于组合模式常涉及递归调用(如遍历整个树形结构),对于深度较大的结构,性能开销可能会比较高。

(4)难以进行复杂的操作

对于一些复杂的操作(例如需要根据组合内的所有元素进行特殊处理的情况),组合模式可能显得无力,需要在客户端额外处理。


组合模式主要角色

(1)抽象角色(Component)

这是组合模式的核心,它定义了树叶和树枝构件的公共接口,并可能提供一些默认行为。在透明式的组合模式中,它还声明了访问和管理子类的接口;而在安全式的组合模式中,这些管理工作由树枝构件完成。

(2)树叶角色(Leaf)

这个角色代表了组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件。简单来说,树叶构件是基本的对象,没有进一步分解的部分。

(3)树枝角色(Composite)

这个角色是组合中的分支节点对象,它有子节点,同样用于继承和实现抽象构件。树枝构件的主要作用是存储和管理子部件,通常包含添加、删除、获取子节点的方法。


组合模式应用场景

主要用于处理具有层次结构的对象系统,特别适合表示“部分-整体”关系。

(1)文件系统

文件系统中的文件夹可以包含文件或其他文件夹,文件是叶子节点,文件夹则是组合节点。

应用:使用组合模式将文件和文件夹抽象成同一个接口,使得客户端可以统一处理单个文件和文件夹,无需区分它们,进行如创建、删除、显示内容等操作。

(2)图形绘制系统

在图形绘制系统中,图形可以由基本图形(如圆、矩形等)和组合图形(由多个基本图形组成)构成。

应用:使用组合模式将基本图形和组合图形抽象成同一个接口,使得客户端可以统一地进行绘制、缩放、移动等操作。

(3)UI 组件树

在用户界面系统中,界面元素(如按钮、文本框、窗口等)具有嵌套结构,容器可以包含子组件,子组件又可以是容器或叶子节点。

应用:通过组合模式,将每个组件(无论是容器组件还是单个组件)都视为统一的接口进行操作,使得整个UI组件树的处理变得一致。

(4)企业组织结构

企业组织中,部门可以包含子部门或者员工,部门和员工有不同的职责和层次,但都属于组织的一部分。

应用:使用组合模式将部门和员工抽象为相同的接口,使得企业可以通过层次化的方式管理员工和部门,进行如计算总薪资、遍历所有人员等操作。

(5)菜单系统

在应用程序中,菜单项可以是单个的菜单项,也可以是子菜单,子菜单中可以包含更多的菜单项。

应用:通过组合模式,菜单项和子菜单都实现同样的接口,客户端可以统一遍历整个菜单系统,进行如显示、点击等操作。

(6)产品目录管理

电子商务系统中的产品目录可能包含子目录和具体的商品,子目录可以包含更多的子目录或商品。

应用:通过组合模式,目录和商品都抽象成统一接口,客户端可以轻松对整个目录树进行遍历,展示或处理商品信息。

(7)任务管理系统

任务管理系统中,任务可以分为单一任务和组合任务,组合任务可以包含多个子任务,子任务也可能是组合任务或单一任务。

应用:通过组合模式,任务和子任务统一抽象为同一接口,系统可以通过遍历的方式进行任务执行、状态跟踪等操作。

(8)权限管理系统

在权限管理系统中,权限可以分为单一权限和组合权限,组合权限可能包含多个子权限,子权限也可以是组合权限或单一权限。

应用:通过组合模式,权限结构可以统一管理,无论是赋予单个权限还是组合权限,系统都可以用一致的方式进行权限校验。


组合模式实现

最常见的例子,公司的人事管理就是一个典型的树状结构,公司的结构是不是这样:
在这里插入图片描述
从这个树状结构上分析,有两种节点:有分支的节点(如研发部经理)和无分支的节点(如员工 A、员工 D 等),我们增加一点学术术语上去,总经理叫做根节点(是不是想到 XML 中的那个根节点 root,那就对了),类似研发部经理有分支的节点叫做树枝节点,类似员工 A 的无分支的节点叫做树叶节点,都很形象,三个类型的的节点,那是不是定义三个类就可以?好,按照这个思路走下去,先看自己设计的类图:

在这里插入图片描述

1、抽象公司类,定义每个员工都有信息

package com.uhhe.common.design.composite;

/**
 * 公司类,定义每个员工都有信息
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/3/1 15:10
 */
public abstract class Corp {

    /**
     * 每个人都有名称
     */
    private final String name;

    /**
     * 每个人都有职位
     */
    private final String position;

    /**
     * 每个人都有薪水,否则谁给你干
     */
    private final int salary;

    /**
     * 父节点是谁
     */
    private Corp parent = null;

    /**
     * 通过接口的方式传递
     *
     * @param name     名称
     * @param position 职位
     * @param salary   薪水
     */
    public Corp(String name, String position, int salary) {
        this.name = name;
        this.position = position;
        this.salary = salary;
    }

    /**
     * 获得员工信息
     *
     * @return 信息
     */
    public String getInfo() {
        String info = "";
        info = "姓名:" + this.name;
        info = info + "\t职位:" + this.position;
        info = info + "\t薪水:" + this.salary;
        return info;
    }

    /**
     * 设置父节点
     *
     * @param parent 父节点
     */
    protected void setParent(Corp parent) {
        this.parent = parent;
    }

    /**
     * 得到父节点
     *
     * @return Corp
     */
    public Corp getParent() {
        return this.parent;
    }

}

2、具体的实现类

(1)普通员工(Leaf)

package com.uhhe.common.design.composite;

/**
 * 普通员工
 * <p>
 * Leaf是树叶节点,在这里就是我们这些小兵
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/3/1 15:11
 */
@SuppressWarnings("all")
public class Leaf extends Corp {

    public Leaf(String name, String position, int salary) {
        super(name, position, salary);
    }

}

(2)领导(Composite)

package com.uhhe.common.design.composite;

import java.util.ArrayList;

/**
 * 节点类,领导
 * <p>
 * 这些树枝节点也就是这些领导们既要有自己的信息,还要知道自己的下属情况
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/3/1 15:15
 */
public class Branch extends Corp {
    /**
     * 领导下边有那些下级领导和小兵
     */
    ArrayList<Corp> subordinateList = new ArrayList<>();

    /**
     * 通过接口的方式传递
     *
     * @param name     名称
     * @param position 职位
     * @param salary   薪水
     */
    public Branch(String name, String position, int salary) {
        super(name, position, salary);
    }

    /**
     * 增加一个下属,可能是小头目,也可能是个小兵
     *
     * @param corp 员工
     */
    public void addSubordinate(Corp corp) {
        corp.setParent(this);
        this.subordinateList.add(corp);
    }

    /**
     * 有哪些下属
     *
     * @return 下属列表
     */
    public ArrayList<Corp> getSubordinate() {
        return this.subordinateList;
    }

}

3、组合模式使用

package com.uhhe.common.design.composite;

import java.util.ArrayList;

/**
 * 组合模式使用
 *
 * @author nizhihao
 * @version 1.0.0
 * @date 2023/3/1 15:57
 */
public class Client {

    /**
     * 组合模式【Composite Pattern】
     * <p>
     * 组合模式在项目中到处都有
     * 比如: 现在的页面结构一般都是上下结构
     * 上面放系统的 Logo,下边分为两部分:左边是导航菜单,右边是展示区,左边的导航菜单一般都是树形的结构,比较清晰,
     * 这个 JavaScript有很多例子,大家可以到网上搜索一把;还有,我们的自己也是一个树状结构,
     * 根据我,能够找到我的父母,根据父亲又能找到爷爷奶奶,根据母亲能够找到外公外婆等等,很典型的树形结构,而且还很规范
     */
    public static void main(String[] args) {
        //首先是组装一个组织结构出来
        Branch ceo = compositeCorpTree();
        //首先把CEO的信息打印出来:
        System.out.println(ceo.getInfo());
        //然后是所有员工信息
        System.out.println(getTreeInfo(ceo));
    }

    /**
     * 把整个树组装出来
     *
     * @return Branch
     */
    public static Branch compositeCorpTree() {
        //首先产生总经理CEO
        Branch root = new Branch("王大麻子", "总经理", 100000);
        //把三个部门经理产生出来
        Branch developDep = new Branch("刘大瘸子", "研发部门经理", 10000);
        Branch salesDep = new Branch("马二拐子", "销售部门经理", 20000);
        Branch financeDep = new Branch("赵三驼子", "财务部经理", 30000);

        //再把三个小组长产生出来
        Branch firstDevGroup = new Branch("杨三乜斜", "开发一组组长", 5000);
        Branch secondDevGroup = new Branch("吴大棒槌", "开发二组组长", 6000);

        //把所有的小兵都产生出来
        Leaf a = new Leaf("a", "开发人员", 2000);
        Leaf b = new Leaf("b", "开发人员", 2000);
        Leaf c = new Leaf("c", "开发人员", 2000);
        Leaf d = new Leaf("d", "开发人员", 2000);
        Leaf e = new Leaf("e", "开发人员", 2000);
        Leaf f = new Leaf("f", "开发人员", 2000);
        Leaf g = new Leaf("g", "开发人员", 2000);
        Leaf h = new Leaf("h", "销售人员", 5000);
        Leaf i = new Leaf("i", "销售人员", 4000);
        Leaf j = new Leaf("j", "财务人员", 5000);
        Leaf k = new Leaf("k", "CEO秘书", 8000);
        Leaf zhengLaoLiu = new Leaf("郑老六", "研发部副经理", 20000);

        // 开始组装
        // CEO下有三个部门经理和一个秘书
        root.addSubordinate(k);
        root.addSubordinate(developDep);
        root.addSubordinate(salesDep);
        root.addSubordinate(financeDep);

        //研发部经理
        developDep.addSubordinate(zhengLaoLiu);
        developDep.addSubordinate(firstDevGroup);
        developDep.addSubordinate(secondDevGroup);

        //看看开发两个开发小组下有什么
        firstDevGroup.addSubordinate(a);
        firstDevGroup.addSubordinate(b);
        firstDevGroup.addSubordinate(c);
        secondDevGroup.addSubordinate(d);
        secondDevGroup.addSubordinate(e);
        secondDevGroup.addSubordinate(f);

        //再看销售部下的人员情况
        salesDep.addSubordinate(h);
        salesDep.addSubordinate(i);

        //最后一个财务
        financeDep.addSubordinate(j);

        return root;
    }

    /**
     * 遍历整棵树,只要给我根节点,我就能遍历出所有的节点
     *
     * @param root 领导
     * @return 树信息
     */
    public static String getTreeInfo(Branch root) {
        ArrayList<Corp> subordinateList = root.getSubordinate();
        String info = "";
        for (Corp s : subordinateList) {
            if (s instanceof Leaf) {
                // 是员工就直接获得信息
                info = info + s.getInfo() + "\n";
            } else {
                // 是个小头目
                info = info + s.getInfo() + "\n" + getTreeInfo((Branch) s);
            }
        }
        return info;
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

未禾

您的支持是我最宝贵的财富!

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

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

打赏作者

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

抵扣说明:

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

余额充值