不学无数——组合模式

组合模式

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

1. 数据结构

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

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

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

广泛的公司组织架构

根据这个树形结构,咱们能够抽象出来两种不一样性质的点:bash

  • 有分支的点数据结构

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

所以按照咱们的思路走下去的,那么能够简单的抽象出三个接口。app

数据结构类图

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

--根节点
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()方法,咱们为何不把它抽象出来呢,还有为何要分根节点和树枝节点呢,根节点本质上也适合树枝节点是同样的。此时咱们就能将以前的接口抽象成以下的。

简化的类图

接口信息以下

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()方法,实现方式也同样,此时咱们彻底能够将其抽象出来。类图表示以下

再次精简的类图

看见这个图,彷佛已是最完美的了,由于减小了不少的工做量,接口也没了,改为了抽象类。省了不少的代码。具体看代码以下

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

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 组合模式的组成

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

组合模式的类图

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

  • 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 透明组合模式

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

透明模式类图

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

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中的实现类

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. 参考文章

相关文章
相关标签/搜索