设计模式系列之组合模式及其在JDK和MyBatis源码中的运用详解

组合模式及其在JDK源码中的运用 前言组合和聚合什么是组合模式示例透明组合模式透明组合模式的缺陷安全组合模式 组合模式角色组合模式在JDK源码中的体现组合模式应用场景享元模式优缺点总结

前言

本文主要会讲述组合模式的用法,并会结合在JDK和MyBatis源码中的运用来进一步理解组合模式。

在编码原则中,有一条是:多用组合,少用继承。当然这里的组合和我们今天要讲的组合模式并不等价,这里的组合其实就是一种聚合,那么聚合和组合有什么区别呢?

组合和聚合

人在一起叫团伙,心在一起叫团队。用这句话来诠释组合与聚合的区别是相对恰当的。

聚合就是说各个对象聚合在一起工作,但是我没有你也行,我照样可以正常运行。但是组合呢,关系就比较密切,组合中的各个对象之间组成了一个整体,缺少了某一个对象就不能正常运行或者说功能会有很大缺陷。
也就是说聚合对象不具备相同生命周期,而组合的对象具有相同的生命周期

举个例子:
比如说电脑和U盘就是聚合,而电脑显示器和主机就是组合。

什么是组合模式

组合模式(Composite Pattern)也称之为整体-部分(Part-Whole)模式。组合模式的核心是通过将单个对象(叶子节点)和组合对象(树枝节点)用相同的接口进行表示,使得单个对象和组合对象的使用具有一致性。组合模式属于结构型模式。

组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,最顶层的节点称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点如下图所示:

在这里插入图片描述

讲了这么多,感觉有点抽象,所以依然是老规矩:Talk is cheap,Show you the code

示例

组合模式有两种写法,分别是透明模式安全模式。下面我们就以高考的科目为例来看看组合模式是如何体现在代码中的

透明组合模式

1、首先建立一个顶层的抽象科目类,这个类中定义了三个通用操作方法,但是均默认不支持操作

package com.zwx.design.pattern.composite.transparency;

/**
 * 顶层抽象组件
 */
public abstract class GkAbstractCourse {
  public void addChild(GkAbstractCourse course){
    System.out.println("不支持添加操作");
  }

  public String getName() throws Exception {
    throw new Exception("不支持获取名称");
  }

  public void info() throws Exception{
    throw new Exception("不支持查询信息操作");
  }
}

PS:这个类中的公共方法之所以不定义为抽象方法的原因是因为假如定义为抽象方法,那么所有的子类都必须重写父类方法,这样体现不出差异性。而这种通过抛异常的方式,如果子类需要用到的功能就重写覆盖父类方法即可。

2、新建一个普通科目类继承通用科目抽象类,这个类作为叶子节点,没有重写addChild方法,也就是这个类属于叶子节点,不支持添加子节点:

package com.zwx.design.pattern.composite.transparency;

/**
 * 普通科目类(叶子节点)
 */
public class CommonCource extends GkAbstractCourse {
  private String name;//课程名称
  private String score;//课程分数

  public CommonCource(String name, String score) {
    this.name = name;
    this.score = score;
  }

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

  @Override
  public void info() {
    System.out.println("课程:" + this.name + ",分数:" + score);
  }
}

3、建立一个具有层级的节点,三个方法都重写了,支持添加子节点,这个类里面为了方便打印的时候看出层级关系,所以我定义了一个层级属性。

package com.zwx.design.pattern.composite.transparency;

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

/**
 * 树枝节点
 */
public class LevelCource extends GkAbstractCourse{
  private List<GkAbstractCourse> courseList = new ArrayList<>();
  private String name;
  private int level;

  public LevelCource(String name, int level) {
    this.name = name;
    this.level = level;
  }

  @Override
  public void addChild(GkAbstractCourse course) {
    courseList.add(course);
  }

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

  @Override
  public void info() throws Exception {
    System.out.println("课程:" + this.name);
    for (GkAbstractCourse course : courseList){
      for (int i=0;i<level;i++){
        System.out.print(" ");
      }
      System.out.print(">");
      course.info();
    }
  }
}

4、建立一个测试类来测试一下:

package com.zwx.design.pattern.composite.transparency;

public class TestTransparency {
  public static void main(String[] args) throws Exception {
    GkAbstractCourse ywCourse = new CommonCource("语文","150");
    GkAbstractCourse sxCourse = new CommonCource("数学","150");
    GkAbstractCourse yyCourse = new CommonCource("英语","150");

    GkAbstractCourse wlCourse = new CommonCource("物理","110");
    GkAbstractCourse hxCourse = new CommonCource("化学","100");
    GkAbstractCourse swCourse = new CommonCource("生物","90");

    GkAbstractCourse lzCourse = new LevelCource("理综",2);
    lzCourse.addChild(wlCourse);
    lzCourse.addChild(hxCourse);
    lzCourse.addChild(swCourse);

    GkAbstractCourse gkCourse = new LevelCource("理科高考科目",1);
    gkCourse.addChild(ywCourse);
    gkCourse.addChild(sxCourse);
    gkCourse.addChild(yyCourse);
    gkCourse.addChild(lzCourse);
    
    gkCourse.info();
  }
}

输出结果:

课程:理科高考科目
  >课程:语文,分数:150
  >课程:数学,分数:150
  >课程:英语,分数:150
  >课程:理综
    >课程:物理,分数:110
    >课程:化学,分数:100
    >课程:生物,分数:90

这里如果用普通科目去调用add方法就会抛出异常,假如上面调用:

swCourse.addChild(ywCourse);

会输出

不支持添加操作

因为在普通科目类里面并没有重写addChild方法。

透明组合模式的缺陷

透明模式的特点就是将组合对象所有的公共方法都定义在了抽象组件内,这样做的好处是客户端无需分辨当前对象是属于树枝节点还是叶子节点,因为它们具备了完全一致的接口,不过缺点就是叶子节点得到到了一些不属于它的方法,比如上面的addChild方法,这违背了接口隔离性原则

安全组合模式

安全组合模式只是规定了系统各个层次的最基础的一致性行为,而把组合(树节点)本身的方法(如树枝节点管理子类的addChild等方法)放到自身当中。

1、首先还是建立一个顶层的抽象根节点(这里面只定义了一个通用的抽象info方法):

package com.zwx.design.pattern.composite.safe;

package com.zwx.design.pattern.composite.safe;

/**
 * 顶层抽象组件
 */
public abstract class GkAbstractCourse {
  protected String name;
  protected String score;

  public GkAbstractCourse(String name, String score) {
    this.name = name;
    this.score = score;
  }

  public abstract void info();
}

2、建立一个叶子节点(这里只是重写了info方法,没有定义其他特有方法):

package com.zwx.design.pattern.composite.safe;

/**
 * 叶子节点
 */
public class CommonCource extends GkAbstractCourse {

  public CommonCource(String name,String score) {
    super(name,score);
  }

  @Override
  public void info() {
    System.out.println("课程:" + this.name + ",分数:" + this.score);
  }
}

3、定义一个树枝节点(这个类当中定义了一个树枝特有的方法addChild):

package com.zwx.design.pattern.composite.safe;

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

/**
 * 树枝节点
 */
public class LevelCource extends GkAbstractCourse{
  private List<GkAbstractCourse> courseList = new ArrayList<>();
  private int level;

  public LevelCource(String name, String score,int level) {
    super(name,score);
    this.level = level;
  }

  public void addChild(GkAbstractCourse course) {
    courseList.add(course);
  }

  @Override
  public void info() {
    System.out.println("课程:" + this.name + ",分数:" + this.score);
    for (GkAbstractCourse course : courseList){
      for (int i=0;i<level;i++){
        System.out.print(" ");
      }
      System.out.print(">");
      course.info();
    }
  }
}

4、新建测试类来测试:

package com.zwx.design.pattern.composite.safe;

public class TestSafe {
  public static void main(String[] args) throws Exception {
    CommonCource ywCourse = new CommonCource("语文","150");
    CommonCource sxCourse = new CommonCource("数学","150");
    CommonCource yyCourse = new CommonCource("英语","150");

    CommonCource wlCourse = new CommonCource("物理","110");
    CommonCource hxCourse = new CommonCource("化学","100");
    CommonCource swCourse = new CommonCource("生物","90");

    LevelCource lzCourse = new LevelCource("理综","300",2);
    lzCourse.addChild(wlCourse);
    lzCourse.addChild(hxCourse);
    lzCourse.addChild(swCourse);

    LevelCource gkCourse = new LevelCource("理科高考","750",1);
    gkCourse.addChild(ywCourse);
    gkCourse.addChild(sxCourse);
    gkCourse.addChild(yyCourse);

    gkCourse.addChild(lzCourse);
    gkCourse.info();
  }
}

输出结果为:

课程:理科高考,分数:750
  >课程:语文,分数:150
  >课程:数学,分数:150
  >课程:英语,分数:150
  >课程:理综,分数:300
    >课程:物理,分数:110
    >课程:化学,分数:100
    >课程:生物,分数:90

这里和透明方式不一样,叶子节点不具备addChild功能,所以无法调用,而上面的示例中时可以被调用,但是调用之后显示不支持,这就是这两种写法最大的区别。

组合模式角色

从上面示例中,可以看到组合模式包含了以下三个角色:

  • 抽象根节点(Component):定义系统各层次对象的公有属性和方法,可以预先定义一些默认行为和属性。
  • 树枝节点(Composite):定义树枝节点的行为,存储子节点,组合树枝节点和叶子节点形成一个树形结构。
  • 叶子节点(Leaf):是系统遍历层次中的最小单位,下面没有子节点。

组合模式在JDK源码中的体现

1、HashMap

HashMap中有一个putAll方法,参数是一个Map,这就是一种组合模式的体现:

在这里插入图片描述

另外还有ArrayList中的addAll方法也是一样。

2、MyBatis中有一个SqlNode接口,下面很多一级标签:

在这里插入图片描述

然后一级标签下面又有二级标签(这就是组合模式的体现):

在这里插入图片描述 

组合模式应用场景

组合模式一般应用在有层级关系的场景,最经典的就是树形菜单,文件和文件夹的管理等

享元模式优缺点

优点:清楚的定义了分层次的复杂对象,让客户端可以忽略层次的差异,方便对整个层次进行动态控制。
缺点:其叶子和树枝的声明是实现类而不是接口,违反了依赖倒置原则,而且组合模式会使设计更加抽象不好理解。

总结

本文主要介绍了组合模式,并介绍了普通的聚合和组合之间的区别,并通过例子详细解释了组合模式中的透明写法和安全写法的区别,最后结合在JDK和MyBatis源码中的运用来进一步理解组合模式的运用。
请关注我,和孤狼一起学习进步

到此这篇关于设计模式系列之组合模式及其在JDK和MyBatis源码中的运用详解的文章就介绍到这了,更多相关组合模式在JDK和MyBatis源码中的运用内容请搜索云海天教程以前的文章或继续浏览下面的相关文章希望大家以后多多支持云海天教程!

原文链接:https://blog.csdn.net/zwx900102/article/details/108554255