組合模式
在DebugMybatis
的源碼時,在DynamicSqlSource.getBoundSql
動態(tài)獲取sql的時候郁岩,Debug會發(fā)現(xiàn)相同的方法但是進(jìn)去的實現(xiàn)類卻不相同滔驶,不明白為什么會這樣春哨,于是上網(wǎng)查了資料說是運(yùn)用了組合的設(shè)計模式嗤形。
1. 數(shù)據(jù)結(jié)構(gòu)
聊組合模式為什么會聊到數(shù)據(jù)結(jié)構(gòu)呢屋吨?看到最后你應(yīng)該就會明白了
相信大家都知道數(shù)據(jù)結(jié)構(gòu)這門學(xué)科桑逝,在數(shù)據(jù)結(jié)構(gòu)中有樹這樣的概念棘劣,樹中會有根節(jié)點、葉子節(jié)點等等楞遏。樹狀的結(jié)構(gòu)在現(xiàn)實生活中應(yīng)用廣泛茬暇,例如我們熟知的XML格式就是一個樹形的結(jié)構(gòu)
說個簡單的例子首昔,在我們身邊常見的,公司的人事管理就是一個典型的樹形結(jié)構(gòu)糙俗。
根據(jù)這個樹形結(jié)構(gòu)沙廉,我們可以抽象出來兩種不同性質(zhì)的點:
-
有分支的點
- 根節(jié)點
- 樹枝節(jié)點
無分支的點:葉子節(jié)點
因此按照我們的思路走下去的,那么可以簡單的抽象出三個接口臼节。
這是最直接能夠想到的類圖表示信息撬陵,但是這個類圖信息目前表示是有些問題的,如果你已經(jīng)看出來這個類圖的缺陷的話网缝,那么這一小部分就可以一目十行跳讀過去了巨税。首先我們先寫出三個接口的代碼:
--根節(jié)點
interface IRoot{
//得到總經(jīng)理的信息
String getInfo();
//根節(jié)點下添加節(jié)點,例如總經(jīng)理下面添加研發(fā)部經(jīng)理
void add(IBranch branch);
//根節(jié)點下添加葉子節(jié)點粉臊,比如添加秘書
void add(ILeaf leaf);
//遍歷手下所有人的信息
List getSubordinateInfo();
}
--樹枝節(jié)點草添,信息同上
interface IBranch{
String getInfo();
void add(IBranch branch);
void add(ILeaf leaf);
List getSubordinateInfo();
}
--葉子節(jié)點,因為葉子節(jié)點已經(jīng)是最底層的了扼仲,所以不能增加任何信息远寸,只能獲得自身的信息
interface ILeaf{
String getInfo();
}
然后看下IRoot的實現(xiàn)類
class Root implements IRoot{
//保存根節(jié)點下的子節(jié)點信息
private List subordinateList=new ArrayList();
//節(jié)點名稱
private String name;
//節(jié)點的薪資
private Integer salary;
//節(jié)點的職位
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;
}
//增加樹枝節(jié)點
@Override
public void add(IBranch branch) {
subordinateList.add(branch);
}
//增加子節(jié)點
@Override
public void add(ILeaf leaf) {
subordinateList.add(leaf);
}
//得到下級的所有信息
@Override
public List getSubordinateInfo() {
return this.subordinateList;
}
}
樹枝節(jié)點Branch
和葉子節(jié)點Leaf
的實現(xiàn)和Root
的實現(xiàn)方式一樣,這里就不一一展示了屠凶。然后我們所有的節(jié)點信息都寫完了驰后,最后我們的工作就是進(jìn)行組裝成一個樹狀結(jié)構(gòu)并且遍歷這棵樹。代碼如下
public static void main(String[] args) {
//根節(jié)點
IRoot ceo = new Root("王大麻子",100000,"總經(jīng)理");
//部門經(jīng)理,樹枝節(jié)點
IBranch developCeo = new Branch("劉大瘸子",50000,"研發(fā)部經(jīng)理");
IBranch saleCeo = new Branch("馬二愣子",50000,"銷售部經(jīng)理");
IBranch finaceCeo = new Branch("趙三駝子",50000,"財務(wù)部經(jīng)理");
//組長,樹枝節(jié)點
IBranch developOne = new Branch("吳大棒槌",20000,"研發(fā)一組組長");
IBranch developTwo = new Branch("鄭老六",20000,"研發(fā)二組組長");
//員工,葉子節(jié)點
ILeaf a = new Leaf("開發(fā)人員A",1000,"開發(fā)");
ILeaf b = new Leaf("開發(fā)人員B",1000,"開發(fā)");
ILeaf c = new Leaf("開發(fā)人員C",1000,"開發(fā)");
ILeaf d = new Leaf("開發(fā)人員D",1000,"開發(fā)");
ILeaf e = new Leaf("開發(fā)人員E",1000,"開發(fā)");
ILeaf f = new Leaf("開發(fā)人員F",1000,"開發(fā)");
ILeaf g = new Leaf("銷售人員G",1000,"銷售");
ILeaf h = new Leaf("銷售人員H",1000,"銷售");
ILeaf i = new Leaf("財務(wù)人員I",1000,"財務(wù)");
ILeaf j = new Leaf("秘書J",1000,"秘書");
//進(jìn)行組裝這個組織架構(gòu)
//總經(jīng)理下的三大得力干將
ceo.add(developCeo);
ceo.add(saleCeo);
ceo.add(finaceCeo);
//總經(jīng)理下的秘書
ceo.add(j);
//研發(fā)部經(jīng)理下的組長
developCeo.add(developOne);
developCeo.add(developTwo);
//銷售部經(jīng)理下的員工
saleCeo.add(g);
saleCeo.add(h);
//財務(wù)部經(jīng)理下的員工
finaceCeo.add(i);
//研發(fā)一組下的員工
developOne.add(a);
developOne.add(b);
developOne.add(c);
//研發(fā)二組下的員工
developTwo.add(d);
developTwo.add(e);
developTwo.add(f);
System.out.println(ceo.getInfo());
//遍歷總經(jīng)理下的所有信息
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());
//遞歸調(diào)用
getAllSubordinateInfo(branch.getSubordinateInfo());
}
}
}
這樣我們就得到了我們想要的樹形結(jié)構(gòu)矗愧,打印信息如下
名稱: 王大麻子 職位是: 總經(jīng)理 薪水是: 100000
名稱: 劉大瘸子 職位是: 研發(fā)部經(jīng)理 薪水是: 50000
名稱: 吳大棒槌 職位是: 研發(fā)一組組長 薪水是: 20000
名稱: 開發(fā)人員A 職位是: 開發(fā) 薪水是: 1000
名稱: 開發(fā)人員B 職位是: 開發(fā) 薪水是: 1000
名稱: 開發(fā)人員C 職位是: 開發(fā) 薪水是: 1000
名稱: 鄭老六 職位是: 研發(fā)二組組長 薪水是: 20000
名稱: 開發(fā)人員D 職位是: 開發(fā) 薪水是: 1000
名稱: 開發(fā)人員E 職位是: 開發(fā) 薪水是: 1000
名稱: 開發(fā)人員F 職位是: 開發(fā) 薪水是: 1000
名稱: 馬二愣子 職位是: 銷售部經(jīng)理 薪水是: 50000
名稱: 銷售人員G 職位是: 銷售 薪水是: 1000
名稱: 銷售人員H 職位是: 銷售 薪水是: 1000
名稱: 趙三駝子 職位是: 財務(wù)部經(jīng)理 薪水是: 50000
名稱: 財務(wù)人員I 職位是: 財務(wù) 薪水是: 1000
名稱: 秘書J 職位是: 秘書 薪水是: 1000
此時我們會發(fā)現(xiàn)灶芝,我們有一大坨的代碼都是公用的,例如每個類中都有getInfo()
方法唉韭,我們?yōu)槭裁床话阉橄蟪鰜砟匾固椋€有為什么要分根節(jié)點和樹枝節(jié)點呢,根節(jié)點本質(zhì)上也是和樹枝節(jié)點是一樣的属愤。此時我們就能將之前的接口抽象成如下的女器。
接口信息如下
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方法合成了一個,因為葉子節(jié)點和樹枝節(jié)點都實現(xiàn)了一樣的接口
@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;
}
}
此時我們經(jīng)過上面的優(yōu)化以后還會覺得有些冗雜,因為在LeafNew
和BranchNew
中還有有一模一樣的代碼住诸。即兩個類中都有重復(fù)的getInfo()
方法驾胆,實現(xiàn)方式也一樣,此時我們完全可以將其抽象出來只壳。類圖表示如下
看見這個圖俏拱,似乎已經(jīng)是最完美的了,因為減少了很多的工作量吼句,接口也沒了锅必,改成了抽象類。省了很多的代碼。具體看代碼如下
首先看一下抽象類抽象出來的公共東西
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方法合成了一個,因為葉子節(jié)點和樹枝節(jié)點都實現(xiàn)了一樣的接口
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);
}
}
而此時在創(chuàng)建樹形結(jié)構(gòu)的時候如下搞隐,和之前創(chuàng)建的沒多大的差別驹愚。
public static void main(String[] args) {
BranchNew ceo = new BranchNew("王大麻子",100000,"總經(jīng)理");
//部門經(jīng)理,樹枝節(jié)點
BranchNew developCeo = new BranchNew("劉大瘸子",50000,"研發(fā)部經(jīng)理");
BranchNew saleCeo = new BranchNew("馬二愣子",50000,"銷售部經(jīng)理");
BranchNew finaceCeo = new BranchNew("趙三駝子",50000,"財務(wù)部經(jīng)理");
//組長,樹枝節(jié)點
BranchNew developOne = new BranchNew("吳大棒槌",20000,"研發(fā)一組組長");
BranchNew developTwo = new BranchNew("鄭老六",20000,"研發(fā)二組組長");
//員工,葉子節(jié)點
LeafNew a = new LeafNew("開發(fā)人員A",1000,"開發(fā)");
LeafNew b = new LeafNew("開發(fā)人員B",1000,"開發(fā)");
LeafNew c = new LeafNew("開發(fā)人員C",1000,"開發(fā)");
LeafNew d = new LeafNew("開發(fā)人員D",1000,"開發(fā)");
LeafNew e = new LeafNew("開發(fā)人員E",1000,"開發(fā)");
LeafNew f = new LeafNew("開發(fā)人員F",1000,"開發(fā)");
LeafNew g = new LeafNew("銷售人員G",1000,"銷售");
LeafNew h = new LeafNew("銷售人員H",1000,"銷售");
LeafNew i = new LeafNew("財務(wù)人員I",1000,"財務(wù)");
LeafNew j = new LeafNew("秘書J",1000,"秘書");
//進(jìn)行組裝這個組織架構(gòu)
//總經(jīng)理下的三大得力干將
ceo.add(developCeo);
ceo.add(saleCeo);
ceo.add(finaceCeo);
//總經(jīng)理下的秘書
ceo.add(j);
//研發(fā)部經(jīng)理下的組長
developCeo.add(developOne);
developCeo.add(developTwo);
//銷售部經(jīng)理下的員工
saleCeo.add(g);
saleCeo.add(h);
//財務(wù)部經(jīng)理下的員工
finaceCeo.add(i);
//研發(fā)一組下的員工
developOne.add(a);
developOne.add(b);
developOne.add(c);
//研發(fā)二組下的員工
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);
}
}
}
此時發(fā)現(xiàn)運(yùn)行結(jié)果和之前的結(jié)果一模一樣,這就是組合模式
2. 什么是組合模式
在剛才的數(shù)據(jù)結(jié)構(gòu)中我們用代碼實現(xiàn)了樹形結(jié)構(gòu)劣纲。這個就是組合模式逢捺。組合模式主要是用來描述部分與整體的關(guān)系。
將對象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu)癞季,使得用戶對單個對象和組合對象的使用具有一致性
2.1 組合模式的組成
其實我們在上面已經(jīng)實現(xiàn)了一個組合模式劫瞳,組合模式的組合就是數(shù)據(jù)結(jié)構(gòu)中樹形結(jié)構(gòu)的組成并且將其代碼簡化,抽象出來樹枝節(jié)點和葉子節(jié)點的公共部分形成抽象類或者接口绷柒,并且通過調(diào)用此抽象類或者接口將組合對象和簡單對象進(jìn)行一致的處理志于。
其中組合模式涉及到了三個角色
- Component:抽象構(gòu)件,定義了參加組合對象的共有方法和屬性废睦。當(dāng)然也可以定義為接口
- Leaf:葉子節(jié)點構(gòu)件伺绽,組合模式中最小的遍歷單位
- Composite:樹枝節(jié)點構(gòu)件,與葉子節(jié)點構(gòu)成一個樹形結(jié)構(gòu)
接下來我們可以寫出實際的組合模式代碼示例嗜湃,首先可以先看抽象的構(gòu)建奈应,它是組合模式的精髓所在
public abstract class Component{
//無論是個體還是整體都是共享此代碼的
public void doSomething(){
//具體的業(yè)務(wù)邏輯代碼
}
}
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
類可以重寫父類的方法。
通過創(chuàng)建場景類模擬創(chuàng)建樹狀的數(shù)據(jù)結(jié)構(gòu)购披,并且通過遞歸的方式遍歷整個樹
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 透明組合模式
組合模式分為兩種杖挣,一種是安全模式,一種是透明模式今瀑。我們上面講的是安全模式程梦,那么透明模式是什么呢?可以看下透明模式的類圖橘荠。
通過類圖的對比我們便可知道,透明模式是將方法都放在抽象類中或者接口中郎逃。透明模式下的葉子節(jié)點和樹枝節(jié)點都會有相同的結(jié)構(gòu)哥童,通過判斷是否他下面還有子節(jié)點可以知道是葉子節(jié)點還是樹枝節(jié)點。
3. MyBatis中的組合模式應(yīng)用
此時我們學(xué)完了組合模式以后就知道了在Mybatis中動態(tài)組裝Sql中用到了組合模式褒翰,那么Mybatis是如何應(yīng)用的呢贮懈。比如下面的一段Sql。
<select id="queryAllDown" resultType="map" parameterType="String">
select * from 表名 where cpt_pro=#{cpt}
<if test="cpt!=''">
and cpt_pro=#{cpt}
</if>
</select>
Mybatis在進(jìn)行XML解析的時候會解析兩個標(biāo)簽优训,一個是select
一個是if
朵你,然后通過SqlNode進(jìn)行解析標(biāo)簽中的內(nèi)容,下面是SqlNode中的實現(xiàn)類
這些類就構(gòu)成了SqlNode樹形結(jié)構(gòu)中的各個節(jié)點揣非。所有的子節(jié)點都是同一類節(jié)點抡医,可以遞歸的向下執(zhí)行。例如StaticTextSqlNode
是最底層的節(jié)點,因此它直接將Sql拼接到sqlBuilder
中忌傻。
@Override
public boolean apply(DynamicContext context) {
for (SqlNode sqlNode : contents) {
sqlNode.apply(context);
}
return true;
}
而如果是碰到了if
標(biāo)簽大脉,那么可以看IfSqlNode
,在IfSqlNode
中會先做表達(dá)式的判斷水孩,如果通過的話镰矿,那么進(jìn)行調(diào)用遞歸解析。如果不通過就直接跳過俘种。
@Override
public boolean apply(DynamicContext context) {
if (evaluator.evaluateBoolean(test, context.getBindings())) {
contents.apply(context);
return true;
}
return false;
}
因此Mybatis就是通過組合模式以一致的方式處理個別對象或者是帶有標(biāo)簽的對象秤标。