1. 程式人生 > 程式設計 >設計模式系列之組合模式及其在JDK和MyBatis原始碼中的運用詳解

設計模式系列之組合模式及其在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
>課程:理綜,分數:300
>課程:物理,分數:90

這裡和透明方式不一樣,葉子節點不具備addChild功能,所以無法呼叫,而上面的示例中時可以被呼叫,但是呼叫之後顯示不支援,這就是這兩種寫法最大的區別。

組合模式角色

從上面示例中,可以看到組合模式包含了以下三個角色:

  • 抽象根節點(Component):定義系統各層次物件的公有屬性和方法,可以預先定義一些預設行為和屬性。
  • 樹枝節點(Composite):定義樹枝節點的行為,儲存子節點,組合樹枝節點和葉子節點形成一個樹形結構。
  • 葉子節點(Leaf):是系統遍歷層次中的最小單位,下面沒有子節點。

組合模式在JDK原始碼中的體現

1、HashMap

HashMap中有一個putAll方法,引數是一個Map,這就是一種組合模式的體現:

在這裡插入圖片描述

另外還有ArrayList中的addAll方法也是一樣。

2、MyBatis中有一個SqlNode介面,下面很多一級標籤:

在這裡插入圖片描述

然後一級標籤下面又有二級標籤(這就是組合模式的體現):

在這裡插入圖片描述

組合模式應用場景

組合模式一般應用在有層級關係的場景,最經典的就是樹形選單,檔案和資料夾的管理等

享元模式優缺點

優點:清楚的定義了分層次的複雜物件,讓客戶端可以忽略層次的差異,方便對整個層次進行動態控制。
缺點:其葉子和樹枝的宣告是實現類而不是介面,違反了依賴倒置原則,而且組合模式會使設計更加抽象不好理解。

總結

本文主要介紹了組合模式,並介紹了普通的聚合和組合之間的區別,並通過例子詳細解釋了組合模式中的透明寫法和安全寫法的區別,最後結合在JDK和MyBatis原始碼中的運用來進一步理解組合模式的運用。
請關注我,和孤狼一起學習進步

到此這篇關於設計模式系列之組合模式及其在JDK和MyBatis原始碼中的運用詳解的文章就介紹到這了,更多相關組合模式在JDK和MyBatis原始碼中的運用內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!