设计模式内容分享(十):组合模式

设计模式内容分享(十):组合模式组合模式是一种解决树状问题的结构型模式 再使用过程中需要有较强的层次结构 在实现时要注意树枝节点的特有接口以及含有内部属性 List List 里面放 Component

大家好,欢迎来到IT知识分享网。

目录

一、组合模式是什么

二、组合模式的适用场景

三、组合模式结构

3.1 组合模式主要角色

3.1 组合模式的两种类型

四、组合模式实现方式

五、组合模式的两种实现

5.1 透明式的组合模式

5.2 安全式的组合模式

5.3 组合模式的扩展

六、组合模式的优缺点

七、组合模式和其他模式的关联

八、总结


一、组合模式是什么

组合模式:又叫作整体-部分(Part-Whole)模式,它将对象组合成树状的层次结构,用来表示整体-部分的关系,使用户对单个对象和组合对象具有一致的访问性,属于结构型模式。

树状结构如下:

777238153cd540f09f61e65da7f9861c.png

由上图可以看出,

  • 根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用。
  • 叶子节点与树枝节点在语义上不属于用一种类型。在组合模式中,会把树枝节点和叶子节点看作属于同一种数据类型(用统一接口定义),让它们具备一致行为。

组合模式在树型结构中,模糊了简单元素(叶子节点)和复杂元素(树枝节点)的概念,客户程序可以像处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

二、组合模式的适用场景

适用场景

  • 需要体现部分与整体的树状层次结构时,可以使用组合模式。
  • 希望客户端忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。

现实案例:

  • 文件夹

    文件夹可以有子文件夹和文件。

  • 超市购物袋

    大袋子装商品和小袋子,小袋子可以装小袋子和商品

  • 算术表达式

    包括操作数、操作符和另一个操作数,另一个操作数也可以是操作数、操作符和另一个操作数

三、组合模式结构

3.1 组合模式主要角色

  • 抽象构件(Component)角色
    • 主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为
    • 透明式的组合模式中抽象构件声明访问和管理子类的接口
  • 安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成,只定义一些通用的方法。
  • 树叶构件(Leaf)角色

    在组合中表示叶节点,叶节点没有子节点,用于继承或实现抽象构件基本行为。

  • 树枝构件(Composite)角色 / 中间构件

    是组合中的分支节点对象,有子节点,用于继承和实现抽象构件基本行为,它的主要作用是存储存储子部件并在Component接口实现与子部件有关的操作。

  • 客户端(Client)

    通过Component接口操作组合部件的对象。

3.1 组合模式的两种类型

组合模式分为透明式的组合模式和安全式的组合模式。这两种类型的主要区别在于抽象构件(Component)角色上的差别。

  • 透明式的组合模式

    在透明式的组合模式中,由于抽象构件声明了所有子类中的全部方法,所以客户端无须区别树叶对象和树枝对象,对客户端来说是透明的。

    • 透明式缺点

      树叶构件本身没有子节点,但是由于继承抽象构件,需要实现树枝构件所特有的行为(比如文件夹的新增子文件夹的方法),此时只能空实现或抛异常。其结构图如下

    • 568c63c97ef74272bca0ad70a6401a8b.png
  • 安全式的组合模式

    在安全式的组合模式中,将管理子构件的方法移到树枝构件中,抽象构件只定义树枝构件和树叶构件所共同的方法。避免了透明式的组合模式的空实现或抛异常问题。

    • 安全式的缺点

      由于叶子节点和树枝节点有不同的行为方法,客户端在调用时要知道树叶对象和树枝对象的存在,所以对对于客户端失去了透明性。其结构图如下

e9117c0fc13147f7b71f295f559ed6da.png

四、组合模式实现方式

组合模式实现的前提:确保应用的核心模型能够以树状结构表示,并将其分解为简单元素和容器,容器必须能够同时包含简单元素和子容器

  • 定义一个接口或抽象类作为抽象构件角色,声明组件接口及其一系列方法。
  • 定义一个叶节点类表示简单元素。实现抽象构件角色, 程序中可以有多个不同的叶节点类。

    叶节点可以作为一个接口,以不同的叶子结点类去实现接口

  • 定义一个树枝节点(容器)类表示复杂元素,实现抽象构件角色
    • 在该类中创建一个数组成员变量来存储对于其子元素的引用。 该数组必须能够同时保存叶节点和容器, 因此必须将其声明为组合接口类型
    • 树枝节点也可以作为一个接口,以不同的树枝节点类去实现接口

五、组合模式的两种实现

  • 【案例】用组合模式实现在超市购物后,显示并计算所选商品信息与总价。
  • 【案例说明】张三在超市购物,购物清单如下
    • 1号小袋子装了2 包芒果干(单价15.8元),1包薯片(单价9.8元)
    • 2号小袋子装了3 包山楂(单价7.8元),2包牛肉脯(单价19.8元)
    • 中型袋子装了1号小袋子,1盒巧克力(单价39.8元)
    • 大型袋子装了中型袋子,2号小袋子,1箱牛奶(单价79.8元)
  • 【大袋子的东西】
    { 1箱牛奶(单价79.8元) 2号小袋子{ 3 包山楂(单价7.8元 2包牛肉脯(单价19.8元) } 中型袋子:{ 1盒巧克力(单价39.8元) 1号小袋子:{ 2 包芒果干(单价15.8元) 1包薯片(单价9.8元) } } } 

案例结构图如下

ad1f37a2a5ca4dc2b938feac84df1cb0.png

 

5.1 透明式的组合模式

透明式的组合模式中抽象构件声明访问和管理子类的接口

  • 抽象构件(Component)角色
    / * 抽象构件(Component)角色 */ publicinterface Article { / * 树枝构件特有的方法: 访问和管理子类的接口 大袋子装小袋子 */ public void add(Article article); / * 计算价格 */ public Double calculation(); / * 显示商品 */ public void show(); } 
  • 树叶构件(Leaf)角色
    / * 树叶构件: 商品 */ publicclass Goods implements Article { / * 商品名称 */ private String name; / * 购买数量 */ private Integer quantity; / * 商品单价 */ private Double unitPrice; public Goods(String name, Integer quantity, Double unitPrice) { this.name = name; this.quantity = quantity; this.unitPrice = unitPrice; } / * 树枝构件特有的方法 * 在树叶构件中是能空实现或者抛异常 */ @Override public void add(Article article) { } @Override public Double calculation() { returnthis.unitPrice * this.quantity; } @Override public void show() { System.out.println(name + ": (数量:" + quantity + ",单价:" + unitPrice + "元)," + "合计:"+this.unitPrice * this.quantity+"元"); } } 
  • 树枝构件(Composite)角色 / 中间构件
    import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; / * 树枝构件: 袋子 */ publicclass Bag implements Article{ / * 袋子名字 */ private String name; public Bag(String name) { this.name = name; } / * 袋子中的商品 */ private List<Article> bags = new ArrayList<Article>(); / * 往袋子中添加袋子或者商品 */ @Override public void add(Article article) { bags.add(article); } @Override public Double calculation() { AtomicReference<Double> sum = new AtomicReference<>(0.0); bags.forEach(e->{ sum.updateAndGet(v -> v + e.calculation()); }); return sum.get(); } @Override public void show() { bags.forEach(Article::show); } } 
  • 客户端代码实现
    public static void main(String[] args) throws Exception { Article smallOneBag = new Bag("1号小袋子"); smallOneBag.add(new Goods("芒果干", 2, 15.8)); smallOneBag.add(new Goods("薯片", 1, 9.8)); Article smallTwoBag = new Bag("2号小袋子"); smallTwoBag.add(new Goods("山楂", 3, 7.8)); smallTwoBag.add(new Goods("牛肉脯", 2, 19.8)); Article mediumBag = new Bag("中袋子"); mediumBag.add(new Goods("巧克力", 1, 39.8)); mediumBag.add(smallOneBag); Article BigBag = new Bag("大袋子"); BigBag.add(new Goods("牛奶", 1, 79.8)); BigBag.add(mediumBag); BigBag.add(smallTwoBag); System.out.println("张三选购的商品有:"); BigBag.show(); Double sum = BigBag.calculation(); System.out.println("要支付的总价是:" + sum + "元"); } 

    以上客户端代码中  new Bag(),new Goods()的引用都是Article,无须区别树叶对象和树枝对象,对客户端来说是透明的,此时Article调用add()是空实现或抛异常的(案例是空实现)。

  • 案例结果输出

13a3b4df98ba4312a2f424619f640976.png

5.2 安全式的组合模式

安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成,只定义一些通用的方法。

  • 抽象构件(Component)角色
    / * 抽象构件(Component)角色 */ publicinterface Article { / * 计算价格 */ public Double calculation(); / * 显示商品 */ public void show(); } 
  • 树叶构件(Leaf)角色
    / * 树叶构件: 商品 */ publicclass Goods implements Article { / * 商品名称 */ private String name; / * 购买数量 */ private Integer quantity; / * 商品单价 */ private Double unitPrice; public Goods(String name, Integer quantity, Double unitPrice) { this.name = name; this.quantity = quantity; this.unitPrice = unitPrice; } @Override public Double calculation() { returnthis.unitPrice * this.quantity; } @Override public void show() { System.out.println(name + ": (数量:" + quantity + ",单价:" + unitPrice + "元)," + "合计:"+this.unitPrice * this.quantity+"元"); } } 
  • 树枝构件(Composite)角色 / 中间构件
    import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicReference; / * 树枝构件: 袋子 */ publicclass Bag implements Article{ / * 袋子名字 */ private String name; public Bag(String name) { this.name = name; } / * 袋子中的商品 */ private List<Article> bags = new ArrayList<Article>(); / * 树枝构件特有的方法: 访问和管理子类的接口 大袋子装小袋子 * 往袋子中添加袋子或者商品 */ @Override public void add(Article article) { bags.add(article); } @Override public Double calculation() { AtomicReference<Double> sum = new AtomicReference<>(0.0); bags.forEach(e->{ sum.updateAndGet(v -> v + e.calculation()); }); return sum.get(); } @Override public void show() { bags.forEach(Article::show); } } 
  • 客户端代码实现
    public static void main(String[] args) throws Exception { Bag smallOneBag = new Bag("1号小袋子"); smallOneBag.add(new Goods("芒果干", 2, 15.8)); smallOneBag.add(new Goods("薯片", 1, 9.8)); Bag smallTwoBag = new Bag("2号小袋子"); smallTwoBag.add(new Goods("山楂", 3, 7.8)); smallTwoBag.add(new Goods("牛肉脯", 2, 19.8)); Bag mediumBag = new Bag("中袋子"); mediumBag.add(new Goods("巧克力", 1, 39.8)); mediumBag.add(smallOneBag); Bag BigBag = new Bag("大袋子"); BigBag.add(new Goods("牛奶", 1, 79.8)); BigBag.add(mediumBag); BigBag.add(smallTwoBag); System.out.println("张三选购的商品有:"); BigBag.show(); Double sum = BigBag.calculation(); System.out.println("要支付的总价是:" + sum + "元"); } 

    以上客户端代码中  new Bag(),new Goods()的引用都是Bag,Goods,客户端在调用时要知道树叶对象和树枝对象的存在。此时只有Bag才能调用add()。

  • 案例结果输出

7033c0984fc7460ebdc40e5a3da5917d.png

5.3 组合模式的扩展

在实际开发过程中,可以对树叶节点和树枝节点分别进行抽象,通过继承的方式让不同的树叶节点和树枝节点子类来实现行为。

95788c8881224f2f86d36d9f7492909e.png

六、组合模式的优缺点

优点

  • 可以利用多态和递归机制更方便地使用复杂树结构
  • 开闭原则:在组合体内加入新的对象,客户端不会更改源代码,可以一致地处理单个对象和组合对象。

缺点

  1. 设计较复杂,需要明确类之间的层次关系;
  2. 不容易限制容器中的构件;
  3. 不容易用继承的方法来增加构件的新功能;

七、组合模式和其他模式的关联

  • 创建复杂组合树时使用生成器模式,使构造步骤以递归的方式运行。
  • 责任链模式一般和组合模式结合使用。
  • 迭代器模式可以用来遍历组合树。
  • 访问者模式可以用来对整个组合树执行操作。
  • 享元模式可以用来实现组合树的共享叶节点以节省内存。
  • 组合模式和装饰模式的结构图很相似, 两者都依赖递归组合来组织无限数量的对象。
    • 装饰模式只有一个子组件,并且为被封装对象添加了额外的职责
    • 组合模式只是操作对子节点的原有行为得到结果。

八、总结

组合模式是一种解决树状问题的结构型模式,再使用过程中需要有较强的层次结构,在实现时要注意树枝节点的特有接口以及含有内部属性 List,List里面放 Component。

 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/121804.html

(0)
上一篇 2025-10-21 14:15
下一篇 2025-10-21 14:20

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信