本篇介绍结构型模式中的组合模式。
组合模式
组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。掌握组合模式的重点是要理解清楚 “部分/整体” 还有 ”单个对象“ 与 "组合对象" 的含义。 --百度百科
简单来说就是组合模式是专门针对树结构的,树结构中的树节点与叶子节点是不同的对象,树节点中会包括其他树节点或者叶子节点,叶子节点不包含任何其他子节点。这使得我们在对树结构进行遍历的时候必须区分开树节点和叶子节点,并且树节点保存子节点的引用也要区分树节点和叶子节点。我们自然而然的就想到把树节点和叶子节点抽象出来,组合模式就是这样做的。使用组合模式后,节点类型对客户端来说是透明的。
某公司组织结构如下:
每个领导都有招聘和开除自己手下员工的权利,最底层员工啥权利都没有,全部员工都需要打卡且领导打卡后可以看到自己手下的打卡记录(就是遍历组织架构)。
类图
- 抽象组件角色:Employee类,叶子对象与组合对象的公共接口,提供了访问和管理叶子对象和组合对象的方法,有些方法为叶子对象提供了缺省实现;
- 组合对象角色:Manager类,保存所有子节点的引用,要实现抽象组件角色的全部方法,因为除组件对象与叶子对象的公共方法(clockIn()方法)外的全部方法都是为组件对象管理或访问子节点准备的;
- 叶子对象角色:OfficeWorker类,只需要实现抽象方法。
具体实现
抽象组件角色(雇员类):
abstract class Employee {
protected String position;
protected Integer level;
public Employee(String position,Integer level) {
this.position = position;
this.level = level;
}
//招聘
public void recruit(Employee employee) {
throw new UnsupportedOperationException("你没有权利招聘...");
}
//解雇
public void fire(Employee employee) {
throw new UnsupportedOperationException("你没有权利解雇...");
}
//获取员工资料
public List<Employee> getEmployeeList() {
throw new UnsupportedOperationException("你手下没有员工,你就是最底层员工...");
}
//打卡
abstract void clockIn();
}
组合对象角色(领导类):
class Manager extends Employee{
private List<Employee> employeeList;
public Manager(String position,Integer level) {
super(position,level);
employeeList = new ArrayList<>();
}
@Override
void clockIn() {
for (int i=0;i<level;i++){
System.out.print(" ");
}
System.out.println(position+"打卡上班啦...");
for (Employee employee:employeeList) {
employee.clockIn();
}
}
@Override
public void recruit(Employee employee) {
this.employeeList.add(employee);
}
@Override
public void fire(Employee employee) {
this.employeeList.remove(employee);
}
@Override
public List<Employee> getEmployeeList() {
return this.employeeList;
}
}
叶子对象角色(职员类):
class OfficeWorker extends Employee {
public OfficeWorker(String position,Integer level) {
super(position,level);
}
@Override
void clockIn() {
for (int i=0;i<level;i++){
System.out.print(" ");
}
System.out.println(position+"打卡上班啦...");
}
}
测试程序及输出结果:
//测试程序
public static void main(String[] args) {
Employee generalManager = new Manager("总经理",0);
Employee marketingManager = new Manager("营销部经理",1);
Employee itManager = new Manager("it部经理",1);
Employee financeManager = new Manager("财务部经理",1);
Employee projectManager = new Manager("项目经理",2);
Employee salesperson = new OfficeWorker("销售员",3);
Employee webDeveloper = new OfficeWorker("前端开发工程师",3);
Employee backDeveloper = new OfficeWorker("后端开发工程师",3);
Employee accounting = new OfficeWorker("会计",3);
generalManager.recruit(marketingManager);
generalManager.recruit(itManager);
generalManager.recruit(financeManager);
itManager.recruit(projectManager);
marketingManager.recruit(salesperson);
projectManager.recruit(webDeveloper);
projectManager.recruit(backDeveloper);
financeManager.recruit(accounting);
generalManager.clockIn();
projectManager.recruit(new OfficeWorker("测试",3));
System.out.println("项目经理招了测试之后手下已经有"+projectManager.getEmployeeList().size()+"个人啦...");
System.out.println("后端开发工程师实在忙不过来了,也想招人...");
backDeveloper.recruit(new OfficeWorker("后端开发工程师",3));
}
//输出结果
总经理打卡上班啦...
营销部经理打卡上班啦...
销售员打卡上班啦...
it部经理打卡上班啦...
项目经理打卡上班啦...
前端开发工程师打卡上班啦...
后端开发工程师打卡上班啦...
财务部经理打卡上班啦...
会计打卡上班啦...
项目经理招了测试之后手下已经有3个人啦...
后端开发工程师实在忙不过来了,也想招人...
Exception in thread "main" java.lang.UnsupportedOperationException: 你没有权利招聘...
透明模式和安全模式
所谓透明模式是说对客户端来说叶子对象和组合对象都是抽象组件,在上例中对打卡机来说领导和职员都是公司的雇员,明显上面的实现就是透明模式。
安全模式是说不会出现上例中后端工程师可以招聘的情况,写法是把所有对子节点的操作都放到组合对象中,抽象组件对象只保留组合对象与叶子对象的公共方法。
这两种模式各有优劣,透明模式就会出现上例中调用叶子对象对子节点的操作而抛异常的情况(不满足单一责任原则),但因为叶子对象与组合对象的方法都是相同的,因此类型对于客户端来说是透明的,客户端不需要分辨叶子对象和组合对象;安全模式避免了叶子对象的误操作,但客户端需要辨别叶子对象与组合对象(不满足依赖倒置原则)。
事实上组合模式的目的就是让叶子对象和组合对象“看上去一样”,因此还是推荐使用透明模式。
多说一句,代码的设计就像这两种模式一样,很多时候都不能满足所有的设计模式,我们还是需要根据具体的业务场景来设计代码结构。