每日一个设计模式之【组合模式】
☁️前言🎉🎉🎉
大家好✋,我是知识汲取者😄,今天给大家带来一篇有关组合模式的学习笔记。众所周知能够熟练使用设计模式是一个优秀程序猿的必备技能,当我们在项目中选择一个或多个合适的设计模式,不仅能大大提高项目的稳健性、可移植性、可维护性,同时还能让你的代码更加精炼,具备艺术美感。
组合模式是一种很神奇的模式,它可以让用户无需关心当前是组合对象、还是单个对象,从而大大提高程序的灵活性。通过本文您可以知道组合模式是什么(What)?为什么要使用组合模式(Why)组合模式要怎么使用(How)?话不多说,让我们一起来学习吧(●ˇ∀ˇ●)
推荐阅读:
🌻组合模式概述
组合模式是什么?
组合模式(Composite Pattern),又叫部分整体模式,是一种结构型模式,它组合多个对象形成树形结构以表示具有“整体—部分”关系的层次结
组合模式的作用:让单个对象和组合对象的使用具有一致性
组合模式的优缺点
优点:
- 让复杂对象更加清晰。组合模式可以清晰的定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制
- 简化客户端代码。客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构
- 提高了系统的扩展性、维护性。在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”
- 提高了系统的灵活性。组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单
……
缺点:
- 在增加新构件时很难对容器中的构件类型进行限制。有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂
……
组合模式的适用场景:
- 在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,客户端可以一致地对待它们
- 在一个使用面向对象语言开发的系统中需要处理一个树形结构
- 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型
……
Java中的应用:我们适用Document获取结点时,就是使用了组合模式;Mybatis在处理动态SQL节点时,应用到了组合设计模式,MyBatis会将映射文件中定义的静态SQL节点、文本节点等解析成对应的SqlNode实现,形成树形结构;JavaSE中的AWT和Swing包的设计也用到了组合模式
组合模式的角色划分
- 抽象构件(Component):该角色中可以包含所有子类共有行为的声明和实现,是容器构件和叶子构件的抽象层,可以是接口或接口,在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等
- 容器构件(Composite):该角色充当容器作用,内部定义一个集合,可以存存储叶子构件和容器构件,是组合模式的核心,一般是一个具体的类
- 叶子构件(Leaf):该角色实现了在抽象构件中定义的行为,是一个具体的类
组合模式的分类
- 透明组合模式:透明组合模式中,抽象构件角色中声明了所有对于管理成员对象的方法,透明组合模式是组合模式的标准形式
- 安全组合模式:安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在容器构件类中声明并实现这些方法
🌱组合模式的实现
🍀透明组合模式
- 实现方式:继承和实现
- 存在的问题:违反反了单一职责原则与接口隔离原则让 Leaf 类继承了它本不应该有的方法,这就导致当用户可以通过Leaf对象调用它没有实现的方法,这样很容易出现异常,十分不安全(当然也可以直接提供相应的错误处理代码防止异常导致程序中断)
示例:
问题描述:公司组织结构 在学习和使用组合模式时,Sunny软件公司开发人员发现树形结构其实随处可见,例如Sunny公司的组织结构就是“一棵标准的树”,如图所示:
![]()
在Sunny软件公司的内部办公系统Sunny OA系统中,有一个与公司组织结构对应的树形菜单,行政人员可以给各级单位下发通知,这些单位可以是总公司的一个部门,也可以是一个分公司,还可以是分公司的一个部门。用户只需要选择一个根节点即可实现通知的下发操作,而无须关心具体的实现细节。
温馨提示:通过上面的类图可以发现测试类(这里模拟客户端)交互的类太多了,这是由于在Test中要创建大量的对象,其实这里也可以使用工厂模式进行优化,让测试类只与工厂类进行交互,从而进一步降低耦合度,但这里只是为了体验组合模式就不改造了,感兴趣的读者可以自行改造
Step1:创建抽象构件
Unit:
package com.hhxy.composite.transparent; /** * @author ghp * @date 2022/10/7 * @title 公司和部门的抽象类 * @description */ public abstract class Unit { /* * 发通知的方法 */ public abstract void notice(); /** * 添加部门或公司的方法 */ public abstract void add(unit unit); /** * 移除部门或公司的方法 */ public abstract void remove(int i); }
Step2:创建容器构件
Company:
package com.hhxy.composite.transparent.ext; import com.hhxy.composite.transparent.Unit; import java.util.ArrayList; import java.util.List; /** * @author ghp * @date 2022/10/7 * @title 公司(这是一个容器) * @description 用于存储公司对象或者部门对象,是组合模式的核心 */ public class Company extends Unit { private String name; private List<Unit> companies = new ArrayList<>(); public Company(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * 发通知的方法 */ @Override public void notice() { for (Unit company : companies) { company.notice(); } } /** * 添加部门或公司的方法 * * @param company */ @Override public void add(Unit company) { companies.add(company); } /** * 移除部门或公司的方法 * * @param i */ @Override public void remove(int i) { companies.remove(i); } }
Step3:创建叶子构件
1)FinanceDpartment:
package com.hhxy.composite.transparent.ext; import com.hhxy.composite.transparent.Unit; /** * @author ghp * @date 2022/10/7 * @title 财务部 * @description */ public class FinanceDepartment extends Unit { private String name; public FinanceDepartment(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * 发通知的方法 */ @Override public void notice() { System.out.println(name+"财务部发了通知"); } /** * 添加部门或公司的方法 * * @param unit */ @Override public void add(Unit unit) { System.out.println("不支持该方法"); } /** * 移除部门或公司的方法 * * @param i */ @Override public void remove(int i) { System.out.println("不支持该方法"); } }
2)HumanResourceDepartment:
和FinanceDpartmen的代码类似,具体代码请参考Gitee或Github仓库,略……
3)ResercheDepartment:
和FinanceDpartmen的代码类似,具体代码请参考Gitee或Github仓库,略……
Step4:测试
package com.hhxy.test; import com.hhxy.composite.transparent.Unit; import com.hhxy.composite.transparent.ext.Company; import com.hhxy.composite.transparent.ext.FinanceDepartment; import com.hhxy.composite.transparent.ext.HumanResourcesDepartment; import com.hhxy.composite.transparent.ext.ResearchDepartment; /** * @author ghp * @date 2022/10/7 * @title 测试类1 * @description 用于测试透明组合模式 */ public class Test1 { public static void main(String[] args) { //创建三家公司对象,Sunny总公司、上海分公司、深圳总公司 Unit company1,company2,company3; //创建部门对象,财务部、人力资源部、研发部 Unit department1,department2,department3,department4,department5,department6,department7,department8,department9; company1 = new Company("Sunny总公司"); company2 = new Company("上海分公司"); company3 = new Company("深圳分公司"); company1.add(company2); company1.add(company3); department1 = new FinanceDepartment("Sunny总公司"); department2 = new HumanResourcesDepartment("Sunny总公司"); department3 = new ResearchDepartment("Sunny总公司"); company1.add(department1); company1.add(department2); company1.add(department3); department4 = new FinanceDepartment("上海分公司"); department5 = new HumanResourcesDepartment("上海分公司"); department6 = new ResearchDepartment("上海分公司"); company2.add(department4); company2.add(department5); company2.add(department6); department7 = new FinanceDepartment("深圳分公司"); department8 = new HumanResourcesDepartment("深圳分公司"); department9 = new ResearchDepartment("深圳分公司"); company3.add(department7); company3.add(department8); company3.add(department9); //总公司发通知 company1.notice(); System.out.println("-----------------------------------"); //上海分公司发通知 company2.notice(); System.out.println("-----------------------------------"); //深圳分公司发通知 company3.notice(); System.out.println("-----------------------------------"); //模拟用户操作失误 department1.add(department2); } }
测试结果:
🍀安全组合模式
- 实现方式:继承和实现
- 解决的问题:Leaf中存在不属于自己的方法
- 存在的问题:违反了”依赖倒置原则“,不能面对抽象编程,让程序对于用户不够透明
示例:
在透明组合模式的示例基础上进行改进,相对于透明组合模式而言,该进的地方是:在抽象构件中,我们不定义所有的行为,而是只定义叶子构件和容器构件所公有的行为,也就是Notice方法。容器构件独有的方法只在容器构件中定义并进行实现,这样就能保障用户无法使用叶子构件对象去调用到容器构件的特有方法了
Step1:创建抽象构件
Util:
package com.hhxy.composite.safe; /** * @author ghp * @date 2022/10/9 * @title * @description */ public abstract class Unit { /** * 发通知的方法 */ public abstract void notice(); }
Step2:创建容器构件
Company:
package com.hhxy.composite.safe.ext; import com.hhxy.composite.safe.Unit; import java.util.ArrayList; import java.util.List; /** * @author ghp * @date 2022/10/9 * @title 公司类(是一个容器) * @description 用于存储公司对象和部门对象 */ public class Company extends Unit { private String name; private List<Unit> companies = new ArrayList<>(); public Company(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * 发通知的方法 */ @Override public void notice() { for (Unit company : companies) { company.notice(); } } /** * 添加部门或公司的方法 */ public void add(Unit company) { companies.add(company); } /** * 移除部门或公司的方法 */ public void remove(int i) { companies.remove(i); } }
Step3:创建叶子构件
1)FinanceDpartment:
package com.hhxy.composite.safe.ext; import com.hhxy.composite.safe.Unit; /** * @author ghp * @date 2022/10/7 * @title 财务部 * @description */ public class FinanceDepartment extends Unit { private String name; public FinanceDepartment(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * 发通知的方法 */ @Override public void notice() { System.out.println(name+"财务部发了通知"); } }
2)HumanResourceDepartment:
和FinanceDpartmen的代码类似,具体代码请参考Gitee或Github仓库,略……
3)ResercheDepartment:
和FinanceDpartmen的代码类似,具体代码请参考Gitee或Github仓库,略……
Step4:测试
package com.hhxy.test; import com.hhxy.composite.safe.Unit; import com.hhxy.composite.safe.ext.*; /** * @author ghp * @date 2022/10/9 * @title 测试类2 * @description 用于测试安全组合模式 */ public class Test2 { public static void main(String[] args) { //创建三家公司对象,Sunny总公司、上海分公司、深圳总公司 Company company1,company2,company3; //创建部门对象,财务部、人力资源部、研发部 Unit department1,department2,department3,department4,department5,department6,department7,department8,department9; company1 = new Company("Sunny总公司"); company2 = new Company("上海分公司"); company3 = new Company("深圳分公司"); company1.add(company2); company1.add(company3); department1 = new FinanceDepartment("Sunny总公司"); department2 = new HumanResourcesDepartment("Sunny总公司"); department3 = new ResearchDepartment("Sunny总公司"); company1.add(department1); company1.add(department2); company1.add(department3); department4 = new FinanceDepartment("上海分公司"); department5 = new HumanResourcesDepartment("上海分公司"); department6 = new ResearchDepartment("上海分公司"); company2.add(department4); company2.add(department5); company2.add(department6); department7 = new FinanceDepartment("深圳分公司"); department8 = new HumanResourcesDepartment("深圳分公司"); department9 = new ResearchDepartment("深圳分公司"); company3.add(department7); company3.add(department8); company3.add(department9); //总公司发通知 company1.notice(); System.out.println("-----------------------------------"); //上海分公司发通知 company2.notice(); System.out.println("-----------------------------------"); //深圳分公司发通知 company3.notice(); //模拟用户操作失误 // department1.add(department2);//直接在编译阶段报错,这样就能防止用户的误操作 } }
测试结果:
🌲总结
组合模式是一种结构型模式,它让对象和对象之间形成树形结构,在Java中应用很广泛,并拥有两种类别的组合模式
透明组合模式,面对抽象编程,符合“依赖倒置原则”,让程序对用户透明,用户不必关心具体实现,降低了程序的使用难度。但是却不符合”单一职责原则“与”接口隔离原则“,叶子构件拥有不属于它的方法,用户容易操作失误,需要增加异常处理代码、
安全组合模式,虽然解决了安全性问题,但是他违背了“依赖依赖导致原则”,没有面对抽象编程,增加了系统的维护成本
总的来讲两种类别的组合模式,各有优劣,面对具体情况、具体分析,我们要做的就是熟悉它们各自的特点,掌握它们的实现方式,面对类似问题,可以选择一种合适的组合模式来解决问题😃
自此,文章就结束了,如果觉得本文对你有一丢丢帮助的话😄,欢迎点赞👍+评论✍,您的支持将是我写出更加优秀文章的动力O(∩_∩)O
上一篇:每日一个设计模式之【桥接模式】
下一篇:每日一个设计模式之【装饰模式】
参考文章:
在次致谢