目录
组合模式【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;
}
}