网站首页 > 技术文章 正文
前言
《设计模式自习室》系列,顾名思义,本系列文章带你温习常见的设计模式。主要内容有:
- 该模式的介绍,包括: 引子、意图(大白话解释) 类图、时序图(理论规范)
- 该模式的代码示例:熟悉该模式的代码长什么样子
- 该模式的优缺点:模式不是万金油,不可以滥用模式
- 该模式的应用案例:了解它在哪些重要的源码中被使用
该系列会逐步更新于我的博客和公众号(博客见文章底部),也希望各位观众老爷能够关注我的个人公众号:后端技术漫谈,不会错过精彩好看的文章。
系列文章回顾
- 【设计模式自习室】开篇:为什么我们要用设计模式?
- 【设计模式自习室】建造者模式
- 【设计模式自习室】原型模式
- 【设计模式自习室】透彻理解单例模式
- 【设计模式自习室】理解工厂模式的三种形式
- 【设计模式自习室】适配器模式
- 【设计模式自习室】装饰模式
- 【设计模式自习室】桥接模式 Bridge Pattern:处理多维度变化
- 【设计模式自习室】门面模式 Facade Pattern
- 【设计模式自习室】享元模式 Flyweight Pattern:减少对象数量
- 【设计模式自习室】详解代理模式
结构型——组合模式 Composite
引子
组合模式是为了表示那些层次结构,同时部分和整体也可能是一样的结构,常见的如文件夹或者树。
上图来自:
https://www.cnblogs.com/betterboyz/p/9356458.html
从上图可以看出,文件系统是一个树结构,树上长有节点。树的节点有两种,一种是树枝节点,即目录,有内部树结构,在图中涂有颜色;另一种是文件,即树叶节点,没有内部树结构。
定义
组合模式定义了如何将容器对象和叶子对象进行递归组合,使得客户在使用的过程中无须进行区分,可以对他们进行一致的处理。
在使用组合模式中需要注意几点也是组合模式最关键的地方:叶子对象和组合对象实现相同的接口。这就是组合模式能够将叶子节点和对象节点进行一致处理的原因。
合成模式的实现根据所实现接口的区别分为两种形式,分别称为安全式和透明式,将在类图一节中详细介绍两种形式。
类图
安全式合成模式
安全模式的合成模式要求管理聚集的方法只出现在树枝构件类中,而不出现在树叶构件类中。
- Component 抽象构件:组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理Component子部件。
- Composite 树枝构件:是组合中的分支节点对象,它有子节点。树枝构件类给出所有的管理子对象的方法,如add()、remove()以及getChild()。
- Leaf 树叶构件:叶子对象,叶子结点没有子结点。
透明式合成模式
与安全式的合成模式不同的是,透明式的合成模式要求所有的具体构件类,不论树枝构件还是树叶构件,均符合一个固定接口。
代码实现
安全式合成模式
- 抽象构件角色类 Component
public?interface?Component?{
????/**
?????*?输出组建自身的名称
?????*/
????public?void?printStruct(String?preStr);
}
- 树枝构件角色类 Composite
public?class?Composite?implements?Component?{
????/**
?????*?用来存储组合对象中包含的子组件对象
?????*/
????private?List<Component>?childComponents?=?new?ArrayList<Component>();
????/**
?????*?组合对象的名字
?????*/
????private?String?name;
????/**
?????*?构造方法,传入组合对象的名字
?????*?@param?name????组合对象的名字
?????*/
????public?Composite(String?name){
????????this.name?=?name;
????}
????/**
?????*?聚集管理方法,增加一个子构件对象
?????*?@param?child?子构件对象
?????*/
????public?void?addChild(Component?child){
????????childComponents.add(child);
????}
????/**
?????*?聚集管理方法,删除一个子构件对象
?????*?@param?index?子构件对象的下标
?????*/
????public?void?removeChild(int?index){
????????childComponents.remove(index);
????}
????/**
?????*?聚集管理方法,返回所有子构件对象
?????*/
????public?List<Component>?getChild(){
????????return?childComponents;
????}
????/**
?????*?输出对象的自身结构
?????*?@param?preStr?前缀,主要是按照层级拼接空格,实现向后缩进
?????*/
????@Override
????public?void?printStruct(String?preStr)?{
????????//?先把自己输出
????????System.out.println(preStr?+?"+"?+?this.name);
????????//如果还包含有子组件,那么就输出这些子组件对象
????????if(this.childComponents?!=?null){
????????????//添加两个空格,表示向后缩进两个空格
????????????preStr?+=?"??";
????????????//输出当前对象的子对象
????????????for(Component?c?:?childComponents){
????????????????//递归输出每个子对象
????????????????c.printStruct(preStr);
????????????}
????????}
????}
}
- 树叶构件角色类 Leaf
public?class?Leaf?implements?Component?{
????/**
?????*?叶子对象的名字
?????*/
????private?String?name;
????/**
?????*?构造方法,传入叶子对象的名称
?????*?@param?name?叶子对象的名字
?????*/
????public?Leaf(String?name){
????????this.name?=?name;
????}
????/**
?????*?输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字
?????*?@param?preStr?前缀,主要是按照层级拼接的空格,实现向后缩进
?????*/
????@Override
????public?void?printStruct(String?preStr)?{
????????//?TODO?Auto-generated?method?stub
????????System.out.println(preStr?+?"-"?+?name);
????}
}
客户端调用:
public?class?Client?{
????public?static?void?main(String[]args){
????????Composite?root?=?new?Composite("服装");
????????Composite?c1?=?new?Composite("男装");
????????Composite?c2?=?new?Composite("女装");
????????Leaf?leaf1?=?new?Leaf("衬衫");
????????Leaf?leaf2?=?new?Leaf("夹克");
????????Leaf?leaf3?=?new?Leaf("裙子");
????????Leaf?leaf4?=?new?Leaf("套装");
????????root.addChild(c1);
????????root.addChild(c2);
????????c1.addChild(leaf1);
????????c1.addChild(leaf2);
????????c2.addChild(leaf3);
????????c2.addChild(leaf4);
????????root.printStruct("");
????}
}
透明式合成模式
相比上面的安全式只有两处改动:
- Composite :implements Conponent改为extends Conponent,其他地方无变化。
public?class?Composite?extends?Component?{
????...
}
- Leaf:此类将implements Conponent改为extends Conponent,其他地方无变化。
public?class?Leaf?extends?Component?{
????...
}
客户端调用:
public?class?Client?{
????public?static?void?main(String[]args){
????????Component?root?=?new?Composite("服装");
????????Component?c1?=?new?Composite("男装");
????????Component?c2?=?new?Composite("女装");
????????Component?leaf1?=?new?Leaf("衬衫");
????????Component?leaf2?=?new?Leaf("夹克");
????????Component?leaf3?=?new?Leaf("裙子");
????????Component?leaf4?=?new?Leaf("套装");
????????root.addChild(c1);
????????root.addChild(c2);
????????c1.addChild(leaf1);
????????c1.addChild(leaf2);
????????c2.addChild(leaf3);
????????c2.addChild(leaf4);
????????root.printStruct("");
????}
}
可以看出,客户端无需再区分操作的是树枝对象(Composite)还是树叶对象(Leaf)了;对于客户端而言,操作的都是Component对象。
使用场景
Java集合中的组合模式
HashMap 提供 putAll 的方法,可以将另一个 Map 对象放入自己的存储空间中,如果有相同的 key 值则会覆盖之前的 key 值所对应的 value 值
public?class?Test?{
????public?static?void?main(String[]?args)?{
????????Map<String,?Integer>?map1?=?new?HashMap<String,?Integer>();
????????map1.put("aa",?1);
????????map1.put("bb",?2);
????????map1.put("cc",?3);
????????System.out.println("map1:?"?+?map1);
????????Map<String,?Integer>?map2?=?new?LinkedMap();
????????map2.put("cc",?4);
????????map2.put("dd",?5);
????????System.out.println("map2:?"?+?map2);
????????map1.putAll(map2);
????????System.out.println("map1.putAll(map2):?"?+?map1);
????}
}
更多应用场景可参考:
https://blog.csdn.net/wwwdc1012/article/details/82945703
- java.awt中的组合模式
- Mybatis SqlNode中的组合模式
优缺点
优点
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”
缺点
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系
- 不容易限制容器中的构件
- 不容易用继承的方法来增加构件的新功能
两种合成模式:安全性合成模式和透明性合成模式的优劣
- 安全性合成模式是指:从客户端使用合成模式上看是否更安全,如果是安全的,那么就不会有发生误操作的可能,能访问的方法都是被支持的。
- 透明性合成模式是指:从客户端使用合成模式上,是否需要区分到底是“树枝对象”还是“树叶对象”。如果是透明的,那就不用区分,对于客户而言,都是Compoent对象,具体的类型对于客户端而言是透明的,是无须关心的。
对于合成模式而言,在安全性和透明性上,会更看重透明性,毕竟合成模式的目的是:让客户端不再区分操作的是树枝对象还是树叶对象,而是以一个统一的方式来操作。
而且对于安全性的实现,需要区分是树枝对象还是树叶对象。有时候,需要将对象进行类型转换,却发现类型信息丢失了,只好强行转换,这种类型转换必然是不够安全的。
因此在使用合成模式的时候,建议多采用透明性的实现方式。
参考
- 《Java与模式》
- https://www.cnblogs.com/lfxiao/p/6816026.html
- https://www.jianshu.com/p/3a1885d26dff
- https://www.cnblogs.com/betterboyz/p/9356458.html
关注我
我是一名后端开发工程师。
主要关注后端开发,数据安全,爬虫,物联网,边缘计算等方向,欢迎交流。
各大平台都可以找到我
- 微信公众号:后端技术漫谈
- Github:@qqxx6661
- CSDN:@Rude3knife
- 知乎:@后端技术漫谈
- 简书:@后端技术漫谈
- 掘金:@蛮三刀把刀
原创博客主要内容
- Java面试知识点复习全手册
- 设计模式/数据结构
- LeetCode/剑指offer 算法题解析
- SpringBoot/SpringCloud菜鸟入门实战系列
- 爬虫相关技术文章
- 后端开发相关技术文章
- 逸闻趣事/好书分享/个人生活
个人公众号:后端技术漫谈
公众号:后端技术漫谈.jpg
如果文章对你有帮助,不妨收藏,投币,转发,在看起来~
猜你喜欢
- 2024-11-06 「数据结构和算法」超详细,超多图解,树的各种概念汇总
- 2024-11-06 Python超全干货:「二叉树」基础知识大全
- 2024-11-06 建议收藏!便于巩固基础,二叉树各种遍历方式我都帮你总结了
- 2024-11-06 python算法基础之分支界限法 python通过什么来判断操作是否在分支结构中
- 2024-11-06 数据结构错题收录(十九) 数据结构题集解析
- 2024-11-06 计算机二级公共知识第一章 数据结构与算法
- 2024-11-06 计算机专业基础综合历年真题试卷汇编
- 2024-11-06 常见的网络拓扑结构,你都看懂吗 常见网络拓扑结构有哪几种?
- 2024-11-06 设计模式21-Interpreter(解析器)模式-四则运算
- 2024-11-06 面试官:为什么选择B+树作为数据库索引结构?谈谈你的理解
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- oraclesql优化 (66)
- 类的加载机制 (75)
- feignclient (62)
- 一致性hash算法 (71)
- dockfile (66)
- 锁机制 (57)
- javaresponse (60)
- 查看hive版本 (59)
- phpworkerman (57)
- spark算子 (58)
- vue双向绑定的原理 (68)
- springbootget请求 (58)
- docker网络三种模式 (67)
- spring控制反转 (71)
- data:image/jpeg (69)
- base64 (69)
- java分页 (64)
- kibanadocker (60)
- qabstracttablemodel (62)
- java生成pdf文件 (69)
- deletelater (62)
- com.aspose.words (58)
- android.mk (62)
- qopengl (73)
- epoch_millis (61)
本文暂时没有评论,来添加一个吧(●'◡'●)