组合模式
1、组合模式介绍
组合模式(Composite Pattern),又叫部分整体模式,是一种结构型设计模式。它可以将一组相似的对象看作一个单一的对象,并在树形结构中组合这些对象,用来表示部分以及整体层次关系。这种模式创建了一个包含自己对象组的类。 其中,叶子节点表示组合中的单个对象,而容器节点则表示包含其他节点的复合对象。容器节点和叶子节点可以被看作是同一种类型的对象,因此可以对它们进行同样的操作。这种模式常被用于处理树形数据结构和递归结构的问题。
1.1 组合模式基本实现
组合模式(Composite),将对象组合成树形结构以表示’部分-整 体’的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一 致性。
组合模式结构图:
Component为组合中的对象声明接口,在适当情况下,实现所有类共有接 口的默认行为。声明一个接口用于访问和管理Component的子部件。
/** * @author Shier * CreateTime 2023/5/10 17:16 */ public abstract class Component { protected String name; public Component(String name) { this.name = name; } /** * 增加节点 * * @param component */ public abstract void add(Component component); /** * 移除 * * @param component */ public abstract void remove(Component component); /** * 树结构 */ public abstract void display(int depth); }
Leaf在组合中表示叶节点对象,叶节点没有子节点。
/** * @author Shier * CreateTime 2023/5/10 17:20 */ public class Leaf extends Component { public Leaf(String name) { super(name); } @Override public void add(Component component) { System.out.println("此节点为叶子节点,不能新增子叶子节点"); } @Override public void remove(Component component) { System.out.println("此节点为叶子节点,不能删除子叶子节点"); } @Override public void display(int depth) { // 叶节点具体显示方法,显示名称和级别 for (int i = 0; i < depth; i++) { System.out.print("-"); } System.out.println(name); } }
Composite定义有枝节点行为,用来存储子部件,在Component接口中实 现与子部件有关的操作,比如增加add和删除remove。
/** * @author Shier * CreateTime 2023/5/10 17:23 */ public class Composite extends Component { /** * 子对象集合依赖存储下属的枝节点和叶子节点 */ private List<Component> childrenList = new ArrayList<Component>(); public Composite(String name) { super(name); } @Override public void add(Component component) { childrenList.add(component); } @Override public void remove(Component component) { childrenList.remove(component); } @Override public void display(int depth) { // 叶节点具体显示方法,显示名称和级别 for (int i = 0; i < depth; i++) { System.out.print("-"); } System.out.println(name); // 遍历下级节点 for (Component component : childrenList) { component.display(depth + 1); } } }
客户端代码,能通过Component接口操作组合部件的对象。
/** * @author Shier * CreateTime 2023/5/10 17:27 */ public class CombinationClient { public static void main(String[] args) { // 根节点 Composite root = new Composite("root"); root.add(new Leaf("Leaf A")); root.add(new Leaf("Leaf B")); // composite1 为root的子节点,其本身下还具有两个叶子节点 Composite composite1 = new Composite("Composite 1"); composite1.add(new Leaf("Leaf 1-A")); composite1.add(new Leaf("Leaf 1-B")); root.add(composite1); // composite1-1 为 composite1 的子节点,其本身下还具有两个叶子节点 Composite composite2 = new Composite("Composite 1-1"); composite2.add(new Leaf("Leaf 1-1-A")); composite2.add(new Leaf("Leaf 1-1-B")); composite1.add(composite2); // 根部还有两个叶子节点 C 和 D Leaf leaf2 = new Leaf("Leaf C"); root.add(leaf2); // 3 被移除了 Leaf leaf3 = new Leaf("Leaf D"); root.add(leaf3); root.remove(leaf3); root.display(1); } }
输出结果:
Leaf类当中也有add和remove,树叶 不是不可以再长分枝吗?
透明方式,也就是说,在Component中声明所有用 来管理子对象的方法,其中包括add、remove等。这样实现Component接口的 所有子类都具备了add和remove。这样做的好处就是叶节点和枝节点对于外 界没有区别,它们具备完全一致的行为接口。但问题也很明显,因为Leaf类 本身不具备add()、remove()方法的功能,所以实现它是没有意义的。那Leaf类当中不用add 和remove方法,可以吗?可以的,那就是使用安全方式。
安全方式,也就是在Component接口中不去声 明add和remove方法,那么子类的Leaf也就不需要去实现它,而是在 Composite中声明所有用来管理子类对象的方法,这样做就不会出现刚才提 到的问题,不过由于不够透明,所以树叶和树枝类将不具有相同的接口,客 户端的调用需要做相应的判断,带来了不便。
2、具体例子说明
例子:使用一个OA系统说明
大公司总部在北京,总部有人力资源部、财务部等,在上海有分部,其他地方还有办事处,南京办事处,杭州办事处
OA系统:
OA系统(Office Automation System)是一种办公自动化系统,它利用计算机技术,将管理、协调和控制等工作自动化。它能够帮助各个部门和员工提高工作效率,降低沟通成本,简化办公流程,实现信息共享和协同工作。
OA系统的功能包括但不限于:电子邮件、日程安排、文档管理、工作流程、会议管理、车辆管理、资产管理等。这些功能能够帮助用户在办公过程中快速、准确、便捷地完成各种任务,提高工作效率。
OA系统还具有安全、稳定、可靠、易扩展等特点,能够适应企业、政府等各种单位的不同需求,并且随着信息化建设的发展,OA系统正在变得越来越智能化和人性化,更好地满足用户的需求。
2.1 公司管理系统
代码结构图
公司类:抽象类或接口
/** * @author Shier * CreateTime 2023/5/10 * 公司抽象类 */ public abstract class Company { protected String name; public Company(String name) { this.name = name; } /** * 增加部门 * * @param component */ public abstract void add(Company component); /** * 移除部门 * * @param component */ public abstract void remove(Company component); /** * 显示公司结构 */ public abstract void display(int depth); /** * 履行职责-不同的部门有不同的职责 */ public abstract void lineOfDuty(); }
具体公司类:实现接口,树枝节点。
/** * @author Shier * CreateTime 2023/5/10 17:23 * 具体分公司 */ public class ConcreteCompany extends Company { /** * 子对象集合依赖存储下属的枝节点和叶子节点 */ private List<Company> childrenList = new ArrayList<>(); public ConcreteCompany(String name) { super(name); } @Override public void add(Company company) { childrenList.add(company); } @Override public void remove(Company company) { childrenList.remove(company); } @Override public void display(int depth) { // 叶节点具体显示方法,显示名称和级别 for (int i = 0; i < depth; i++) { System.out.print("-"); } System.out.println(name); // 遍历下级节点 for (Company component : childrenList) { component.display(depth + 1); } } /** * 履行职责 */ @Override public void lineOfDuty() { for (Company company : childrenList) { company.lineOfDuty(); } } }
人力资源部与财务部类:树叶节点。
/** * @author Shier * CreateTime 2023/5/10 17:20 * 人力资源部,树叶节点 */ public class HRDepartment extends Company { public HRDepartment(String name) { super(name); } /** * 这里是叶子节点,所以说增加和删除什么都不用干 * * @param company */ @Override public void add(Company company) { } @Override public void remove(Company company) { } @Override public void display(int depth) { // 叶节点具体显示方法,显示名称和级别 for (int i = 0; i < depth; i++) { System.out.print("-"); } System.out.println(name); } @Override public void lineOfDuty() { System.out.println(name + " 员工招聘培训管理"); } }
/** * @author Shier * CreateTime 2023/5/10 17:20 * 财务部,树叶节点 */ public class FinanceDepartment extends Company { public FinanceDepartment(String name) { super(name); } /** * 这里是叶子节点,所以说增加和删除什么都不用干 * * @param company */ @Override public void add(Company company) { } @Override public void remove(Company company) { } @Override public void display(int depth) { // 叶节点具体显示方法,显示名称和级别 for (int i = 0; i < depth; i++) { System.out.print("-"); } System.out.println(name); } @Override public void lineOfDuty() { System.out.println(name + " 公司财务收支管理"); } }
客户端:
/** * @author Shier * CreateTime 2023/5/10 17:27 */ public class CompanyClient { public static void main(String[] args) { // 总部公司 ConcreteCompany root = new ConcreteCompany("北京总公司"); root.add(new HRDepartment("总公司人力资源部门")); root.add(new HRDepartment("总公司财务部门")); // 上海分公司 为总公司的子公司,其本身下还具有两个部门 ConcreteCompany composite1 = new ConcreteCompany("上海华东分公司"); composite1.add(new HRDepartment("华东分公司人力资源部")); composite1.add(new HRDepartment("华东分公司财务部")); root.add(composite1); // 南京办事处 为 华东分公司 的公司,其本身下还具有两个部门 ConcreteCompany composite2 = new ConcreteCompany("南京办事处"); composite2.add(new HRDepartment("南京办事处人力资源部门")); composite2.add(new HRDepartment("南京办事处财务部门")); composite1.add(composite2); // 根部还有其他部门 HRDepartment leaf2 = new HRDepartment("其他部门"); root.add(leaf2); System.out.println("公司管理结构图:"); root.display(1); System.out.println("各自职责"); root.lineOfDuty(); } }
输出结果:
组合模式这样就定义了包含人力资源部和财务部这些基本对象和分公司、办事处等组合对象的类层次结构。基本对象可以被组合成更复杂的组合对象,而这个组合对象又可以被组合,这样不断地递归下去,客户代码中, 任何用到基本对象的地方都可以使用组合对象了
用户是不用关心到底是处理一个叶节点还是处理一个组合组 件,也就用不着为定义组合而写一些选择判断语句了
组合模式让客户可以一致地使用组合结构和单个对 象
3、组合模式总结
组合模式优点:
简化客户端代码:组合模式使得客户端可以统一地处理单个对象和组合对象,不需要为处理它们而编写特殊的代码。客户端只需要调用相同的接口,无论是操作单个对象还是操作组合对象。
灵活性和可扩展性:由于组合模式使用了树形结构,可以很方便地添加、删除和修改对象,而不会对现有的代码产生太大的影响。这使得组合模式具有良好的灵活性和可扩展性。
提高代码复用性:组合模式通过将相同的操作应用于单个对象和组合对象,可以提高代码的复用性。不需要为每个对象编写重复的代码,而是可以在组合对象的层次结构中共享代码。
组合模式缺点:
可能会导致设计过度复杂:当对象的层次结构非常复杂时,使用组合模式可能会导致设计变得复杂。同时,过度使用组合模式也会增加系统的复杂性。
不容易限制组合对象的类型:组合模式通常会统一处理单个对象和组合对象,这意味着对组合对象的类型可能没有明确的限制。这可能导致某些操作在组合对象上没有意义或无法正确执行。
组合模式使用场景:
当需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时, 就应考虑用组合模式
当对象呈现出层次结构,并且您希望以统一的方式处理整体和部分时,可以考虑使用组合模式。例如,树形结构、文件系统等。
当您希望客户端能够忽略对象集合和单个对象之间的差异时,可以使用组合模式。客户端只需调用相同的方法,而无需关心操作的是单个对象还是组合对象。
当您希望在不更改现有代码的情况下添加新类型的对象时,可以考虑使用组合模式。组合模式对于扩展性和灵活性非常有利。
可能会导致设计过度复杂:当对象的层次结构非常复杂时,使用组合模式可能会导致设计变得复杂。同时,过度使用组合模式也会增加系统的复杂性。
不容易限制组合对象的类型:组合模式通常会统一处理单个对象和组合对象,这意味着对组合对象的类型可能没有明确的限制。这可能导致某些操作在组合对象上没有意义或无法正确执行。
组合模式使用场景:
当需求中是体现部分与整体层次的结构时,以及你希望用户可以忽略组合对象与单个对象的不同,统一地使用组合结构中的所有对象时, 就应考虑用组合模式
当对象呈现出层次结构,并且您希望以统一的方式处理整体和部分时,可以考虑使用组合模式。例如,树形结构、文件系统等。
当您希望客户端能够忽略对象集合和单个对象之间的差异时,可以使用组合模式。客户端只需调用相同的方法,而无需关心操作的是单个对象还是组合对象。
当您希望在不更改现有代码的情况下添加新类型的对象时,可以考虑使用组合模式。组合模式对于扩展性和灵活性非常有利。