不學無數——組合模式

組合模式

在DebugMybatis的源碼時,在DynamicSqlSource.getBoundSql動態獲取sql的時候,Debug會發現相同的方法可是進去的實現類卻不相同,不明白爲何會這樣,因而上網查了資料說是運用了組合的設計模式。html

1. 數據結構

聊組合模式爲何會聊到數據結構呢?看到最後你應該就會明白了sql

相信你們都知道數據結構這門學科,在數據結構中有樹這樣的概念,樹中會有根節點、葉子節點等等。樹狀的結構在現實生活中應用普遍,例如咱們熟知的XML格式就是一個樹形的結構設計模式

說個簡單的例子,在咱們身邊常見的,公司的人事管理就是一個典型的樹形結構。安全

廣泛的公司組織架構

根據這個樹形結構,咱們能夠抽象出來兩種不一樣性質的點:數據結構

  • 有分支的點架構

    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的實現類ui

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

相關文章
相關標籤/搜索