设计模式(12)结构型模式 - 组合模式

前言

关于结构型模式,学习了适配器模式、桥接模式、装饰模式

适配器模式是通过适配器类将无法使用的类转换成客户需要的类

桥接模式是通过架起一个“桥梁”,将一个事物的两个维度的变化结合起来,两个维度是同级的

装饰模式是在一个事物的基础上,动态的添加一些额外的职责,类似与套娃,在被修饰者身上套上一个装饰,以到达扩展

接下来,学习另一个结构型模式 - 组合模式

现实的问题

在这里插入图片描述

这是一个Linux目录,可能一个目录下有多级目录,怎么去描述所有的目录?

  1. 继承体系
    根目录是最顶级父类,下面的目录就是各种子类
    但是这样太麻烦了,而且也不符合要求,无法实现管理查找,如增删改子目录
  2. 组合树状图
    通过组合的方式,递归展现一个树状图
    这样就可以比较好的描述所有的目录,并且也可以完成管理(毕竟子节点只是聚合在父节点上)

组合模式

组合模式(Composite Pattern):组合多个对象形成树形结构以表示“整体-部分”的结构层次。组合模式对单个对象(即叶子对象)和组合对象(即容器对象)的使用具有一致性

组合模式描述了如何将容器对象(父节点)和叶子对象(子节点)进行递归组合,使得用户在使用时无须对它们进行区分,可以一致地对待容器对象和叶子对象

组合模式又可以称为“整体-部分”(Part-Whole)模式,属于对象的结构模式,它将对象组织到树结构中,可以用来描述整体与部分的关系

在这里插入图片描述

角色:

  • Component: 抽象构件(或者接口)
  • Leaf: 叶子构件
  • Composite: 容器构件
  • Client: 客户类

具体实现案例

对上面Linux目录结构进行一个简单的实现

在这里插入图片描述

叶子节点,容器构件都继承了抽象构件
这样使得叶子节点和容器构件是同一等级的类,处理时也不需要特殊对待

而且容器构件聚合了抽象构件,各个容器构件都是类似的,没有等级之分,使用时就方便多了

package com.company.Structural.CompositePattern;

import java.util.ArrayList;
import java.util.List;

abstract class LinuxDirectory{
    private String name;

    public LinuxDirectory(String name) {
        this.name = name;
    }

    //必须要一个描述方法
    public abstract void description();
    public void add(LinuxDirectory linuxDirectory){
        //因为叶子结点不需要add方法,所以让子类自行决定是否重写
    }
    public void remove(LinuxDirectory linuxDirectory){
        //子类自行决定是否重写
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


}
//根目录
class RootDirectory extends LinuxDirectory{
    //存放子节点
    List<LinuxDirectory> directories = new ArrayList<>();

    public RootDirectory(String name) {
        super(name);
    }

    @Override
    public void add(LinuxDirectory linuxDirectory) {
        //往列表里添加
        directories.add(linuxDirectory);
    }

    @Override
    public void remove(LinuxDirectory linuxDirectory) {
        //从列表删除
        directories.remove(linuxDirectory);
    }

    @Override
    public String getName() {
        return super.getName();
    }

    @Override
    public void setName(String name) {
        super.setName(name);
    }

    @Override
    public void description() {
        System.out.println("--------------"+getName()+"--------------");
        for (LinuxDirectory linuxDirectory : directories){
            linuxDirectory.description();
        }
    }
}
//第二级目录
class SecondDirectory extends LinuxDirectory{

    //存放子节点
    List<LinuxDirectory> directories = new ArrayList<>();

    public SecondDirectory(String name) {
        super(name);
    }

    @Override
    public void add(LinuxDirectory linuxDirectory) {
        //往列表里添加
        directories.add(linuxDirectory);
    }

    @Override
    public void remove(LinuxDirectory linuxDirectory) {
        //从列表删除
        directories.remove(linuxDirectory);
    }

    @Override
    public String getName() {
        return super.getName();
    }

    @Override
    public void setName(String name) {
        super.setName(name);
    }

    @Override
    public void description() {
        System.out.println("--------------"+getName()+"--------------");
        for (LinuxDirectory linuxDirectory : directories){
            linuxDirectory.description();
        }
    }
}
//叶子节点
class LeafDirectory extends LinuxDirectory{
    //叶子结点没有子结点了,仅需一个描述方法
    public LeafDirectory(String name) {
        super(name);
    }
    
    @Override
    public void description() {
        System.out.println(getName());
    }
}


class Test {
    public static void main(String[] args) {
        LinuxDirectory rootDirectory = new RootDirectory("Root");
        //第二层结点
        LinuxDirectory home = new SecondDirectory("home");
        LinuxDirectory etc = new SecondDirectory("etc");
        //第二层结点加入根节点
        rootDirectory.add(home);
        rootDirectory.add(etc);
        //叶子结点加入第二层节点
        home.add(new LeafDirectory("zhangsan"));
        home.add(new LeafDirectory("lisi"));
        etc.add(new LeafDirectory("profile"));
        //打印描述
        rootDirectory.description();
    }
}

在这里插入图片描述

可以轻松的把Linux目录结构描述出来,这就是组合模式的力量

组合模式优缺点

优点

  • 可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,使得增加新构件也更容易。
  • 客户端调用简单,客户端可以一致的使用组合结构或其中单个对象,不用考虑整体部分还是叶子节点部分(都是同一层次)
  • 定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构
  • 更容易在组合体内加入对象构件,客户端不必因为加入了新的对象构件而更改原有代码,修改层次也仅需在客户端改变组合方式即可,符合开闭原则

缺点

  • 使设计变得更加抽象,对象的业务规则如果很复杂,则实现组合模式具有很大挑战性,而且不是所有的方法都与叶子对象子类都有关联(子类不需要父类的方法)
  • 增加新构件时可能会产生一些问题,很难对容器中的构件类型进行限制(具体构件必须符合抽象构件的规范)
  • 如果叶子节点和父类节点有很多不同,如属性、方法不同,明显不能实现组合模式或者会很复杂

组合模式的应用

在jdk中也有组合模式的应用,如swing,javafx,HashMap

看看HashMap

Map<Integer, String> map = new HashMap<>();
map.put(1,"zhangsan");
        
Map<Integer, String> leafMap = new HashMap<>();
leafMap.put(2,"lisi");

map.putAll(leafMap);

使用HashMap时,可以使用putAll方法将另一个HashMap放入自己的Map中(参数类型要一样)

有点类似组合方法,在看看源码:

首先看Map接口:
Map接口定义了方法规范
在这里插入图片描述

Map下有一个抽象实现类:
在这里插入图片描述

这两个层次构成了组合模式的角色:抽象构件

那在看看HashMap的实现:
在这里插入图片描述

putMapEntries是一个具体的实现添加的方法

可以看出HashMap就是一个构件容器,那么叶子节点在哪呢?
Node内部类就是叶子节点
在这里插入图片描述
使用Node来存放值
在这里插入图片描述
HashMap的put方法:put(key,value) - > putVal(key,value) -> 将key,value存放进Node - > 将Node放入HashMap

HashMap虽然没有用一个列表放置子节点,但是创建了一个内部类Node代替

使用场景

  • 需要表示一个对象整体或部分层次,在具有整体和部分的层次结构中,希望通过一种方式忽略整体与部分的差异,可以一致地对待它们
  • 让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节
  • 对象的结构是动态的并且复杂程度不一样,但客户需要一致地处理它们

有结构体系的类,可以化为树状图,使用组合模式就挺好用的

模式扩展

更复杂的组合模式

在这里插入图片描述

如果叶子节点、构件容器还有子节点,组合模式就继续扩展

Java AWT/Swing 中的简单组件 JTextComponent 有子类 JTextField、JTextArea,容器组件 Container 也有子类 Window、Panel

透明组合模式

在这里插入图片描述

透明组合模式是让抽象构件将所有的方法都抽象化,使得子类(leaf、Composite)都必须重写这些方法

透明组合模式,看着很麻烦,为什么要这么做?

好处是在遍历整个树状图可以不用进行强制类型转换

安全组合模式

在这里插入图片描述

也就是我们使用的组合模式,为什么是安全的?
Leaf并没有add等方法,这些方法都是通过构件容器Composite实现

对于透明组合模式与安全组合模式的选择,看是否想要遍历整棵树

总结

  1. 对组合模式进行了一定的了解:组合模式是通过聚合抽象构件在构件容器里,用递归的方式,实现树状图结构表示“整体-部分”的结构层次
  2. 组合模式有3个主要角色:抽象构件、构件容器、叶子构件
  3. 抽象构件定义了方法规范,由于构件容器(树状图父节点)、叶子构件(树状图叶子节点)都继承了抽象构件,所以构件容器和叶子构件是同一层次的类,不需要特殊对待(使用统一的方法)
  4. 构件容器中定义一个容器(集合或者容器类),存放树状图下一层节点,最终使用递归的方法表现出树状图的层次
  5. 组合模式优点:方便对层次结构进行控制、扩展,简化了客户端代码
  6. 组合模式缺点:设计抽象化,新增构件需要符合抽象构件的规范,很难对容器中的构件类型进行限制
  7. 适用情况:需要表示一个对象整体或部分层次;让客户能够忽略不同对象层次的变化,客户端可以针对抽象构件编程,无须关心对象层次结构的细节;对象的结构是动态的并且复杂程度不一样,但客户需要一致地处理它们
  8. 组合模式分为透明组合模式和安全组合模式,区别在于抽象构件是否将方法定义为抽象方法,即叶子节点能否选择自己重写方法,透明组合模式适用于需要遍历树状图,遍历时不需要强制类型转换
发布了109 篇原创文章 · 获赞 31 · 访问量 7370

猜你喜欢

转载自blog.csdn.net/key_768/article/details/105321934
今日推荐