不学无数——组合模式

本文涉及的产品
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 组合模式在DebugMybatis的源码时,在DynamicSqlSource.getBoundSql动态获取sql的时候,Debug会发现相同的方法但是进去的实现类却不相同,不明白为什么会这样,于是上网查了资料说是运用了组合的设计模式。

组合模式

在DebugMybatis的源码时,在DynamicSqlSource.getBoundSql动态获取sql的时候,Debug会发现相同的方法但是进去的实现类却不相同,不明白为什么会这样,于是上网查了资料说是运用了组合的设计模式。

1. 数据结构

聊组合模式为什么会聊到数据结构呢?看到最后你应该就会明白了

相信大家都知道数据结构这门学科,在数据结构中有树这样的概念,树中会有根节点、叶子节点等等。树状的结构在现实生活中应用广泛,例如我们熟知的XML格式就是一个树形的结构

说个简单的例子,在我们身边常见的,公司的人事管理就是一个典型的树形结构。

img_f92dae5a2e3f1d9ac61f4313db4347a8.jpe
普遍的公司组织架构

根据这个树形结构,我们可以抽象出来两种不同性质的点:

  • 有分支的点

    1. 根节点
    2. 树枝节点
  • 无分支的点:叶子节点

因此按照我们的思路走下去的,那么可以简单的抽象出三个接口。

img_82e395ff886c0fac1f5ed016b88f7ce5.png
数据结构类图

这是最直接能够想到的类图表示信息,但是这个类图信息目前表示是有些问题的,如果你已经看出来这个类图的缺陷的话,那么这一小部分就可以一目十行跳读过去了。首先我们先写出三个接口的代码:

--根节点
interface IRoot{
     //得到总经理的信息
    String getInfo();
    //根节点下添加节点,例如总经理下面添加研发部经理
    void add(IBranch branch);
    //根节点下添加叶子节点,比如添加秘书
    void add(ILeaf leaf);
    //遍历手下所有人的信息
    List getSubordinateInfo();
}
--树枝节点,信息同上
interface IBranch{
    String getInfo();
    void add(IBranch branch);
    void add(ILeaf leaf);
    List getSubordinateInfo();
}
--叶子节点,因为叶子节点已经是最底层的了,所以不能增加任何信息,只能获得自身的信息
interface ILeaf{
    String getInfo();
}

然后看下IRoot的实现类

class Root implements IRoot{
     //保存根节点下的子节点信息
    private List subordinateList=new ArrayList();
    //节点名称
    private String name;
    //节点的薪资
    private Integer salary;
    //节点的职位
    private String position;

    public Root(String name, Integer salary, String position) {
        this.name = name;
        this.salary = salary;
        this.position = position;
    }

    @Override
    public String getInfo() {
        String info = "";
        info = "名称: "+this.name;
        info = info + " 职位是: "+ this.position;
        info = info + " 薪水是: "+ this.salary;
        return null;
    }
    //增加树枝节点
    @Override
    public void add(IBranch branch) {
        subordinateList.add(branch);
    }
    //增加子节点
    @Override
    public void add(ILeaf leaf) {
        subordinateList.add(leaf);
    }
    //得到下级的所有信息
    @Override
    public List getSubordinateInfo() {
        return this.subordinateList;
    }
}

树枝节点Branch和叶子节点Leaf的实现和Root的实现方式一样,这里就不一一展示了。然后我们所有的节点信息都写完了,最后我们的工作就是进行组装成一个树状结构并且遍历这棵树。代码如下

public static void main(String[] args) {
        //根节点
        IRoot ceo = new Root("王大麻子",100000,"总经理");
        //部门经理,树枝节点
        IBranch developCeo = new Branch("刘大瘸子",50000,"研发部经理");
        IBranch saleCeo = new Branch("马二愣子",50000,"销售部经理");
        IBranch finaceCeo = new Branch("赵三驼子",50000,"财务部经理");
        //组长,树枝节点
        IBranch developOne = new Branch("吴大棒槌",20000,"研发一组组长");
        IBranch developTwo = new Branch("郑老六",20000,"研发二组组长");
        //员工,叶子节点
        ILeaf a = new Leaf("开发人员A",1000,"开发");
        ILeaf b = new Leaf("开发人员B",1000,"开发");
        ILeaf c = new Leaf("开发人员C",1000,"开发");
        ILeaf d = new Leaf("开发人员D",1000,"开发");
        ILeaf e = new Leaf("开发人员E",1000,"开发");
        ILeaf f = new Leaf("开发人员F",1000,"开发");
        ILeaf g = new Leaf("销售人员G",1000,"销售");
        ILeaf h = new Leaf("销售人员H",1000,"销售");
        ILeaf i = new Leaf("财务人员I",1000,"财务");
        ILeaf j = new Leaf("秘书J",1000,"秘书");
        //进行组装这个组织架构
        //总经理下的三大得力干将
        ceo.add(developCeo);
        ceo.add(saleCeo);
        ceo.add(finaceCeo);
        //总经理下的秘书
        ceo.add(j);
        //研发部经理下的组长
        developCeo.add(developOne);
        developCeo.add(developTwo);
        //销售部经理下的员工
        saleCeo.add(g);
        saleCeo.add(h);
        //财务部经理下的员工
        finaceCeo.add(i);
        //研发一组下的员工
        developOne.add(a);
        developOne.add(b);
        developOne.add(c);
        //研发二组下的员工
        developTwo.add(d);
        developTwo.add(e);
        developTwo.add(f);
        System.out.println(ceo.getInfo());
        //遍历总经理下的所有信息
        getAllSubordinateInfo(ceo.getSubordinateInfo());
    }

    public static void getAllSubordinateInfo(List subordinateList){
        for (int i = 0; i < subordinateList.size(); i++) {
            Object object = subordinateList.get(i);
            if ( object instanceof ILeaf){
                ILeaf leaf = (ILeaf) object;
                System.out.println(leaf.getInfo());
            }
            else {
                IBranch branch = (IBranch) object;
                System.out.println(branch.getInfo());
                //递归调用
                getAllSubordinateInfo(branch.getSubordinateInfo());
            }
        }
    }

这样我们就得到了我们想要的树形结构,打印信息如下

名称: 王大麻子 职位是: 总经理 薪水是: 100000
名称: 刘大瘸子 职位是: 研发部经理 薪水是: 50000
名称: 吴大棒槌 职位是: 研发一组组长 薪水是: 20000
名称: 开发人员A 职位是: 开发 薪水是: 1000
名称: 开发人员B 职位是: 开发 薪水是: 1000
名称: 开发人员C 职位是: 开发 薪水是: 1000
名称: 郑老六 职位是: 研发二组组长 薪水是: 20000
名称: 开发人员D 职位是: 开发 薪水是: 1000
名称: 开发人员E 职位是: 开发 薪水是: 1000
名称: 开发人员F 职位是: 开发 薪水是: 1000
名称: 马二愣子 职位是: 销售部经理 薪水是: 50000
名称: 销售人员G 职位是: 销售 薪水是: 1000
名称: 销售人员H 职位是: 销售 薪水是: 1000
名称: 赵三驼子 职位是: 财务部经理 薪水是: 50000
名称: 财务人员I 职位是: 财务 薪水是: 1000
名称: 秘书J 职位是: 秘书 薪水是: 1000

此时我们会发现,我们有一大坨的代码都是公用的,例如每个类中都有getInfo()方法,我们为什么不把它抽象出来呢,还有为什么要分根节点和树枝节点呢,根节点本质上也是和树枝节点是一样的。此时我们就能将之前的接口抽象成如下的。

img_af87506a4c6d5c173cf588ff6cb34472.png
简化的类图

接口信息如下

interface Info{
    String getInfo();
}

interface ILeafNew extends Info{

}

interface IBranchNew extends Info{
    void add(Info info);
    List getSubordinateInfo();
}

其中BranchNew如下

class BranchNew implements IBranchNew{
    private List subordinateList=new ArrayList();
    private String name;
    private Integer salary;
    private String position;

    public BranchNew(String name, Integer salary, String position) {
        this.name = name;
        this.salary = salary;
        this.position = position;
    }

    @Override
    public String getInfo() {
        String info = "";
        info = "名称: "+this.name;
        info = info + " 职位是: "+ this.position;
        info = info + " 薪水是: "+ this.salary;
        return info;
    }
    //此处将之前的两个add方法合成了一个,因为叶子节点和树枝节点都实现了一样的接口
    @Override
    public void add(Info info) {
        subordinateList.add(info);
    }

    @Override
    public List getSubordinateInfo() {
        return this.subordinateList;
    }
}

其中LeafNew如下

class LeafNew implements ILeafNew{
    private String name;
    private Integer salary;
    private String position;

    public LeafNew(String name, Integer salary, String position) {
        this.name = name;
        this.salary = salary;
        this.position = position;
    }

    @Override
    public String getInfo() {
        String info = "";
        info = "名称: "+this.name;
        info = info + " 职位是: "+ this.position;
        info = info + " 薪水是: "+ this.salary;
        return info;
    }
}

此时我们经过上面的优化以后还会觉得有些冗杂,因为在LeafNewBranchNew中还有有一模一样的代码。即两个类中都有重复的getInfo()方法,实现方式也一样,此时我们完全可以将其抽象出来。类图表示如下

img_13fccf7579538acbbfb6c7593ab99f9b.png
再次精简的类图

看见这个图,似乎已经是最完美的了,因为减少了很多的工作量,接口也没了,改成了抽象类。省了很多的代码。具体看代码如下

首先看一下抽象类抽象出来的公共东西

abstract class Info{
    private String name;
    private Integer salary;
    private String position;

    public Info(String name, Integer salary, String position) {
        this.name = name;
        this.salary = salary;
        this.position = position;
    }
    public String getInfo() {
        String info = "";
        info = "名称: "+this.name;
        info = info + " 职位是: "+ this.position;
        info = info + " 薪水是: "+ this.salary;
        return info;
    }
}

抽象类的下面的两个子类

class BranchNew extends Info{
    private List<Info> subordinateList=new ArrayList();

    public BranchNew(String name, Integer salary, String position) {
       super(name,salary,position);
    }

    //此处将之前的两个add方法合成了一个,因为叶子节点和树枝节点都实现了一样的接口
    public void add(Info info) {
        subordinateList.add(info);
    }

    public List getSubordinateInfo() {
        return this.subordinateList;
    }
}

class LeafNew extends Info{
    public LeafNew(String name, Integer salary, String position) {
       super(name,salary,position);
    }
}

而此时在创建树形结构的时候如下,和之前创建的没多大的差别。

public static void main(String[] args) {
        BranchNew ceo = new BranchNew("王大麻子",100000,"总经理");
        //部门经理,树枝节点
        BranchNew developCeo = new BranchNew("刘大瘸子",50000,"研发部经理");
        BranchNew saleCeo = new BranchNew("马二愣子",50000,"销售部经理");
        BranchNew finaceCeo = new BranchNew("赵三驼子",50000,"财务部经理");
        //组长,树枝节点
        BranchNew developOne = new BranchNew("吴大棒槌",20000,"研发一组组长");
        BranchNew developTwo = new BranchNew("郑老六",20000,"研发二组组长");
        //员工,叶子节点
        LeafNew a = new LeafNew("开发人员A",1000,"开发");
        LeafNew b = new LeafNew("开发人员B",1000,"开发");
        LeafNew c = new LeafNew("开发人员C",1000,"开发");
        LeafNew d = new LeafNew("开发人员D",1000,"开发");
        LeafNew e = new LeafNew("开发人员E",1000,"开发");
        LeafNew f = new LeafNew("开发人员F",1000,"开发");
        LeafNew g = new LeafNew("销售人员G",1000,"销售");
        LeafNew h = new LeafNew("销售人员H",1000,"销售");
        LeafNew i = new LeafNew("财务人员I",1000,"财务");
        LeafNew j = new LeafNew("秘书J",1000,"秘书");
        //进行组装这个组织架构
        //总经理下的三大得力干将
        ceo.add(developCeo);
        ceo.add(saleCeo);
        ceo.add(finaceCeo);
        //总经理下的秘书
        ceo.add(j);
        //研发部经理下的组长
        developCeo.add(developOne);
        developCeo.add(developTwo);
        //销售部经理下的员工
        saleCeo.add(g);
        saleCeo.add(h);
        //财务部经理下的员工
        finaceCeo.add(i);
        //研发一组下的员工
        developOne.add(a);
        developOne.add(b);
        developOne.add(c);
        //研发二组下的员工
        developTwo.add(d);
        developTwo.add(e);
        developTwo.add(f);
        System.out.println(ceo.getInfo());
        getAllList(ceo);
    }

遍历的代码稍微有些变化

public static void getAllList(BranchNew branchNew){
    List<Info> subordinateInfo = branchNew.getSubordinateInfo();
    for (Info info:subordinateInfo){
        if (info instanceof LeafNew){
            System.out.println(info.getInfo());
        }else {
            System.out.println(info.getInfo());
            getAllList((BranchNew) info);
        }
    }
}

此时发现运行结果和之前的结果一模一样,这就是组合模式

2. 什么是组合模式

在刚才的数据结构中我们用代码实现了树形结构。这个就是组合模式。组合模式主要是用来描述部分与整体的关系。

将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性

2.1 组合模式的组成

其实我们在上面已经实现了一个组合模式,组合模式的组合就是数据结构中树形结构的组成并且将其代码简化,抽象出来树枝节点和叶子节点的公共部分形成抽象类或者接口,并且通过调用此抽象类或者接口将组合对象和简单对象进行一致的处理。

img_a38b1f3890780c2fd395381051917111.png
组合模式的类图

其中组合模式涉及到了三个角色

  • Component:抽象构件,定义了参加组合对象的共有方法和属性。当然也可以定义为接口
  • Leaf:叶子节点构件,组合模式中最小的遍历单位
  • Composite:树枝节点构件,与叶子节点构成一个树形结构

接下来我们可以写出实际的组合模式代码示例,首先可以先看抽象的构建,它是组合模式的精髓所在

public abstract class Component{
    //无论是个体还是整体都是共享此代码的
    public void doSomething(){
    //具体的业务逻辑代码
    }
}

Composite

class Composite extends Component{
    List<Component> list = new ArrayList<>();
    void add(Component component){
        list.add(component);
    }
    void remove(Component component){
        list.remove(component);
    }
    List<Component> getChild(){
        return list;
    }
}

通用Leaf类可以重写父类的方法。

通过创建场景类模拟创建树状的数据结构,并且通过递归的方式遍历整个树

public static void main(String[] args) {
    Composite root = new Composite();
    root.doSomething();
    LeafM leafM = new LeafM();
    Composite branch = new Composite();
    root.add(branch);
    branch.add(leafM);
}
//通过递归遍历树
public static void display(Composite composite){
    for (Component component : composite.getChild()){
        if (component instanceof LeafM){
            component.doSomething();
        }else {
            display((Composite) component);
        }
    }
}

2.2 透明组合模式

组合模式分为两种,一种是安全模式,一种是透明模式。我们上面讲的是安全模式,那么透明模式是什么呢?可以看下透明模式的类图。

img_8d3a0305e02804d80648fce3e529a34e.png
透明模式类图

通过类图的对比我们便可知道,透明模式是将方法都放在抽象类中或者接口中。透明模式下的叶子节点和树枝节点都会有相同的结构,通过判断是否他下面还有子节点可以知道是叶子节点还是树枝节点。

3. MyBatis中的组合模式应用

此时我们学完了组合模式以后就知道了在Mybatis中动态组装Sql中用到了组合模式,那么Mybatis是如何应用的呢。比如下面的一段Sql。

<select id="queryAllDown" resultType="map" parameterType="String">
    select * from 表名 where  cpt_pro=#{cpt}
    <if test="cpt!=''">
    and cpt_pro=#{cpt}
    </if>
</select>

Mybatis在进行XML解析的时候会解析两个标签,一个是select一个是if,然后通过SqlNode进行解析标签中的内容,下面是SqlNode中的实现类

img_8a7eb3fc9c8c5d3414caac6e9d97a334.png
SqlNode中的实现类

这些类就构成了SqlNode树形结构中的各个节点。所有的子节点都是同一类节点,可以递归的向下执行。例如StaticTextSqlNode是最底层的节点,因此它直接将Sql拼接到sqlBuilder中。

  @Override
  public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }

而如果是碰到了if标签,那么可以看IfSqlNode,在IfSqlNode中会先做表达式的判断,如果通过的话,那么进行调用递归解析。如果不通过就直接跳过。

@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
  contents.apply(context);
  return true;
}
return false;
}

因此Mybatis就是通过组合模式以一致的方式处理个别对象或者是带有标签的对象。

4. 参考文章

相关文章
|
6月前
|
设计模式 Java 开发者
设计模式揭秘:Java世界的七大奇迹
【4月更文挑战第7天】探索Java设计模式:单例、工厂方法、抽象工厂、建造者、原型、适配器和观察者,助你构建健壮、灵活的软件系统。了解这些模式如何提升代码复用、可维护性,以及在特定场景下的应用,如资源管理、接口兼容和事件监听。掌握设计模式,但也需根据实际情况权衡,打造高效、优雅的软件解决方案。
41 0
|
6月前
|
设计模式 缓存 安全
探索设计模式的魅力:从单一继承到组合模式-软件设计的演变与未来
组合模式:构建灵活树形结构的艺术。 组合模式旨在解决如何将对象组合成树形结构,隐藏具体实现,使客户端对单个对象和复合对象的使用具有一致性。通过将对象组合成树形结构,组合模式提供了层次化的结构,使系统更灵活、可扩展。 核心思想在于统一叶节点和组合节点。叶节点代表具体的对象,而组合节点则是其他对象的容器。该设计允许我们以统一的方式处理叶子和组合,简化了许多操作。实践中,组合模式适用于具有树形结构并且希望保持结构灵活的系统。它不仅提高了代码的可重用性和可维护性,还使得添加新功能变得简单,无需修改现有代码。...
89 0
|
6月前
|
设计模式
|
容器
谈谈对组合模式的看法
谈谈对组合模式的看法
57 0
|
设计模式 NoSQL Java
被误读的设计模式
被误读的设计模式
118 0
被误读的设计模式
|
设计模式 数据采集 算法
还记得设计模式中称霸武林的的六大设计原则吗?
设计模式中称霸武林的的六大设计原则
132 0
还记得设计模式中称霸武林的的六大设计原则吗?
|
设计模式 Java
设计模式铺铺路(面向对象设计的原则一二)
我们的知识星球马上就要开始更新设计模式了,在更新设计模式之前,我们是不是需要做一些准备呢?否则设计模式中一些遵循的原则大家会一头雾水,所以我今天来给大家说一些面向对象的七种原则,有人说是6种有人说是7种,我个人认为是7种,我就按照7种来说,今天我就介绍2种,下一篇文章将会继续介绍剩下的五种原则,这些原则也会在设计模式中出现,各位技术人,欢迎大家的踊跃参加呦。
设计模式铺铺路(面向对象设计的原则一二)
|
设计模式 测试技术 uml
把书读薄 | 《设计模式之美》设计模式与范式(行为型-中介模式)
终于来到行为型设计模式的最后一个,中介模式 (Mediator Pattern),本文对应设计模式与范式:行为型(73)。
171 0
|
设计模式 安全 Java
把书读薄 | 《设计模式之美》设计模式与范式(结构型-组合模式)
本文对应设计模式与范式:结构型(53),组合模式 (Composite Pattern),又称 部分整体模式,不要跟前面讲的类间的 组合关系 混淆!!!组合模式是用来 处理树形结构数据(对象集合) 的。 数据必须要能表示成树形结构,导致日常开发中不怎么常用,但如果数据能满足树形结构,应用此模式有奇效(代码简洁)。
180 0
|
设计模式 算法 Java
把书读薄 | 《设计模式之美》设计模式与范式(结构型-桥接模式)
本文对应设计模式与范式:结构型(49),桥接模式 (Bridge Pattern)。
135 0