本文的主要內容:html
更多內容可訪問個人我的博客:laijianfeng.org java
設計模式 | 簡單工廠模式及典型應用
設計模式 | 工廠方法模式及典型應用
設計模式 | 抽象工廠模式及典型應用
設計模式 | 建造者模式及典型應用
設計模式 | 原型模式及典型應用
設計模式 | 外觀模式及典型應用
設計模式 | 裝飾者模式及典型應用
設計模式 | 適配器模式及典型應用
設計模式 | 享元模式及典型應用sql
樹形結構不論在生活中或者是開發中都是一種很是常見的結構,一個容器對象(如文件夾)下能夠存放多種不一樣的葉子對象或者容器對象,容器對象與葉子對象之間屬性差異可能很是大。編程
因爲容器對象和葉子對象在功能上的區別,在使用這些對象的代碼中必須有區別地對待容器對象和葉子對象,而實際上大多數狀況下咱們但願一致地處理它們,由於對於這些對象的區別對待將會使得程序很是複雜。segmentfault
組合模式爲解決此類問題而誕生,它可讓葉子對象和容器對象的使用具備一致性。設計模式
組合模式(Composite Pattern):組合多個對象造成樹形結構以表示具備 "總體—部分" 關係的層次結構。組合模式對單個對象(即葉子對象)和組合對象(即容器對象)的使用具備一致性,組合模式又能夠稱爲 "總體—部分"(Part-Whole) 模式,它是一種對象結構型模式。安全
因爲在軟件開發中存在大量的樹形結構,所以組合模式是一種使用頻率較高的結構型設計模式,Java SE中的AWT和Swing包的設計就基於組合模式。bash
除此之外,在XML解析、組織結構樹處理、文件系統設計等領域,組合模式都獲得了普遍應用。微信
Component(抽象構件):它能夠是接口或抽象類,爲葉子構件和容器構件對象聲明接口,在該角色中能夠包含全部子類共有行爲的聲明和實現。在抽象構件中定義了訪問及管理它的子構件的方法,如增長子構件、刪除子構件、獲取子構件等。mybatis
Leaf(葉子構件):它在組合結構中表示葉子節點對象,葉子節點沒有子節點,它實現了在抽象構件中定義的行爲。對於那些訪問及管理子構件的方法,能夠經過異常等方式進行處理。
Composite(容器構件):它在組合結構中表示容器節點對象,容器節點包含子節點,其子節點能夠是葉子節點,也能夠是容器節點,它提供一個集合用於存儲子節點,實現了在抽象構件中定義的行爲,包括那些訪問及管理子構件的方法,在其業務方法中能夠遞歸調用其子節點的業務方法。
組合模式的關鍵是定義了一個抽象構件類,它既能夠表明葉子,又能夠表明容器,而客戶端針對該抽象構件類進行編程,無須知道它到底表示的是葉子仍是容器,能夠對其進行統一處理。同時容器對象與抽象構件類之間還創建一個聚合關聯關係,在容器對象中既能夠包含葉子,也能夠包含容器,以此實現遞歸組合,造成一個樹形結構。
咱們來實現一個簡單的目錄樹,有文件夾和文件兩種類型,首先須要一個抽象構件類,聲明瞭文件夾類和文件類須要的方法
public abstract class Component {
public String getName() {
throw new UnsupportedOperationException("不支持獲取名稱操做");
}
public void add(Component component) {
throw new UnsupportedOperationException("不支持添加操做");
}
public void remove(Component component) {
throw new UnsupportedOperationException("不支持刪除操做");
}
public void print() {
throw new UnsupportedOperationException("不支持打印操做");
}
public String getContent() {
throw new UnsupportedOperationException("不支持獲取內容操做");
}
}
複製代碼
實現一個文件夾類 Folder,繼承 Component,定義一個 List<Component>
類型的componentList屬性,用來存儲該文件夾下的文件和子文件夾,並實現 getName、add、remove、print等方法
public class Folder extends Component {
private String name;
private List<Component> componentList = new ArrayList<Component>();
public Folder(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
@Override
public void add(Component component) {
this.componentList.add(component);
}
@Override
public void remove(Component component) {
this.componentList.remove(component);
}
@Override
public void print() {
System.out.println(this.getName());
for (Component component : this.componentList) {
component.print();
}
}
}
複製代碼
文件類 File,繼承Component父類,實現 getName、print、getContent等方法
public class File extends Component {
private String name;
private String content;
public File(String name, String content) {
this.name = name;
this.content = content;
}
@Override
public String getName() {
return this.name;
}
@Override
public void print() {
System.out.println(this.getName());
}
@Override
public String getContent() {
return this.content;
}
}
複製代碼
咱們來測試一下
public class Test {
public static void main(String[] args) {
Folder DSFolder = new Folder("設計模式資料");
File note1 = new File("組合模式筆記.md", "組合模式組合多個對象造成樹形結構以表示具備 \"總體—部分\" 關係的層次結構");
File note2 = new File("工廠方法模式.md", "工廠方法模式定義一個用於建立對象的接口,讓子類決定將哪個類實例化。");
DSFolder.add(note1);
DSFolder.add(note2);
Folder codeFolder = new Folder("樣例代碼");
File readme = new File("README.md", "# 設計模式示例代碼項目");
Folder srcFolder = new Folder("src");
File code1 = new File("組合模式示例.java", "這是組合模式的示例代碼");
srcFolder.add(code1);
codeFolder.add(readme);
codeFolder.add(srcFolder);
DSFolder.add(codeFolder);
DSFolder.print();
}
}
複製代碼
輸出結果
設計模式資料
組合模式筆記.md
工廠方法模式.md
樣例代碼
README.md
src
組合模式示例.java
複製代碼
輸出正常,不過有個小問題,從輸出看不出它們的層級結構,爲了體現出它們之間的層級關係,咱們須要改造一下 Folder 類,增長一個 level 屬性,並修改 print 方法
public class Folder extends Component {
private String name;
private List<Component> componentList = new ArrayList<Component>();
public Integer level;
public Folder(String name) {
this.name = name;
}
@Override
public String getName() {
return this.name;
}
@Override
public void add(Component component) {
this.componentList.add(component);
}
@Override
public void remove(Component component) {
this.componentList.remove(component);
}
@Override
public void print() {
System.out.println(this.getName());
if (this.level == null) {
this.level = 1;
}
String prefix = "";
for (int i = 0; i < this.level; i++) {
prefix += "\t- ";
}
for (Component component : this.componentList) {
if (component instanceof Folder){
((Folder)component).level = this.level + 1;
}
System.out.print(prefix);
component.print();
}
this.level = null;
}
}
複製代碼
如今的輸出就有相應的層級結構了
設計模式資料
- 組合模式筆記.md
- 工廠方法模式.md
- 樣例代碼
- - README.md
- - src
- - - 組合模式示例.java
複製代碼
咱們能夠畫出它們之間的類圖
在這裏父類 Component
是一個抽象構件類,Folder
類是一個容器構件類,File
是一個葉子構件類,Folder 和 File 繼承了 Component,Folder 與 Component 又是聚合關係
在使用組合模式時,根據抽象構件類的定義形式,咱們可將組合模式分爲透明組合模式和安 全組合模式兩種形式。
透明組合模式
透明組合模式中,抽象構件角色中聲明瞭全部用於管理成員對象的方法,譬如在示例中 Component
聲明瞭 add
、remove
方法,這樣作的好處是確保全部的構件類都有相同的接口。透明組合模式也是組合模式的標準形式。
透明組合模式的缺點是不夠安全,由於葉子對象和容器對象在本質上是有區別的,葉子對象不可能有下一個層次的對象,即不可能包含成員對象,所以爲其提供 add()
、remove()
等方法是沒有意義的,這在編譯階段不會出錯,但在運行階段若是調用這些方法可能會出錯(若是沒有提供相應的錯誤處理代碼)
安全組合模式
在安全組合模式中,在抽象構件角色中沒有聲明任何用於管理成員對象的方法,而是在容器構件 Composite
類中聲明並實現這些方法。
安全組合模式的缺點是不夠透明,由於葉子構件和容器構件具備不一樣的方法,且容器構件中那些用於管理成員對象的方法沒有在抽象構件類中定義,所以客戶端不能徹底針對抽象編程,必須有區別地對待葉子構件和容器構件。
在實際應用中 java.awt
和 swing
中的組合模式即爲安全組合模式。
組合模式的主要優勢以下:
組合模式的主要缺點以下:
適用場景:
Java GUI分兩種:
AWT(Abstract Window Toolkit):抽象窗口工具集,是第一代的Java GUI組件。繪製依賴於底層的操做系統。基本的AWT庫處理用戶界面元素的方法是把這些元素的建立和行爲委託給每一個目標平臺上(Windows、 Unix、 Macintosh等)的本地GUI工具進行處理。
Swing,不依賴於底層細節,是輕量級的組件。如今可能是基於Swing來開發。
咱們來看一個AWT的簡單示例:
注意:爲了正常顯示中文,須要在IDEA中的
Edit Configurations -> VM Options
中設置參數-Dfile.encoding=GB18030
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class MyFrame extends Frame {
public MyFrame(String title) {
super(title);
}
public static void main(String[] args) {
MyFrame frame = new MyFrame("這是一個 Frame");
// 定義三個構件,添加到Frame中去
Button button = new Button("按鈕 A");
Label label = new Label("這是一個 AWT Label!");
TextField textField = new TextField("這是一個 AWT TextField!");
frame.add(button, BorderLayout.EAST);
frame.add(label, BorderLayout.SOUTH);
frame.add(textField, BorderLayout.NORTH);
// 定義一個 Panel,在Panel中添加三個構件,而後再把Panel添加到Frame中去
Panel panel = new Panel();
panel.setBackground(Color.pink);
Label lable1 = new Label("用戶名");
TextField textField1 = new TextField("請輸入用戶名:", 20);
Button button1 = new Button("肯定");
panel.add(lable1);
panel.add(textField1);
panel.add(button1);
frame.add(panel, BorderLayout.CENTER);
// 設置Frame的屬性
frame.setSize(500, 300);
frame.setBackground(Color.orange);
// 設置點擊關閉事件
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
frame.setVisible(true);
}
}
複製代碼
運行後窗體顯示以下
咱們在Frame容器中添加了三個不一樣的構件 Button
、Label
、TextField
,還添加了一個 Panel
容器,Panel
容器中又添加了 Button
、Label
、TextField
三個構件,爲何容器 Frame
和 Panel
能夠添加類型不一樣的構件和容器呢?
咱們先來看下AWT Component的類圖
GUI組件根據做用能夠分爲兩種:基本組件和容器組件。
java.awt.Container
的直接或間接子類容器父類 Container
的部分代碼以下
public class Container extends Component {
/**
* The components in this container.
* @see #add
* @see #getComponents
*/
private java.util.List<Component> component = new ArrayList<>();
public Component add(Component comp) {
addImpl(comp, null, -1);
return comp;
}
// 省略...
}
複製代碼
容器父類 Container
內部定義了一個集合用於存儲 Component
對象,而容器組件 Container
和 基本組件如 Button
、Label
、TextField
等都是 Component
的子類,因此能夠很清楚的看到這裏應用了組合模式
Component
類中封裝了組件通用的方法和屬性,如圖形的組件對象、大小、顯示位置、前景色和背景色、邊界、可見性等,所以許多組件類也就繼承了 Component
類的成員方法和成員變量,相應的成員方法包括:
   getComponentAt(int x, int y)
   getFont()
   getForeground()
   getName()
   getSize()
   paint(Graphics g)
   repaint()
   update()
   setVisible(boolean b)
   setSize(Dimension d)
   setName(String name)
複製代碼
HashMap
提供 putAll
的方法,能夠將另外一個 Map
對象放入本身的存儲空間中,若是有相同的 key 值則會覆蓋以前的 key 值所對應的 value 值
public class Test {
public static void main(String[] args) {
Map<String, Integer> map1 = new HashMap<String, Integer>();
map1.put("aa", 1);
map1.put("bb", 2);
map1.put("cc", 3);
System.out.println("map1: " + map1);
Map<String, Integer> map2 = new LinkedMap();
map2.put("cc", 4);
map2.put("dd", 5);
System.out.println("map2: " + map2);
map1.putAll(map2);
System.out.println("map1.putAll(map2): " + map1);
}
}
複製代碼
輸出結果
map1: {aa=1, bb=2, cc=3}
map2: {cc=4, dd=5}
map1.putAll(map2): {aa=1, bb=2, cc=4, dd=5}
複製代碼
查看 putAll
源碼
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
複製代碼
putAll
接收的參數爲父類 Map
類型,因此 HashMap
是一個容器類,Map
的子類爲葉子類,固然若是 Map
的其餘子類也實現了 putAll
方法,那麼它們都既是容器類,又都是葉子類
同理,ArrayList
中的 addAll(Collection<? extends E> c)
方法也是一個組合模式的應用,在此不作探討
MyBatis 的強大特性之一即是它的動態SQL,其經過 if
, choose
, when
, otherwise
, trim
, where
, set
, foreach
標籤,可組合成很是靈活的SQL語句,從而提升開發人員的效率。
來幾個官方示例:
動態SQL -- IF
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
複製代碼
動態SQL -- choose, when, otherwise
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
複製代碼
動態SQL -- where
<select id="findActiveBlogLike" resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
複製代碼
動態SQL -- foreach
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT * FROM POST P WHERE ID in
<foreach item="item" index="index" collection="list"
open="(" separator="," close=")">
#{item}
</foreach>
</select>
複製代碼
Mybatis在處理動態SQL節點時,應用到了組合設計模式,Mybatis會將映射配置文件中定義的動態SQL節點、文本節點等解析成對應的 SqlNode 實現,並造成樹形結構。
SQLNode
的類圖以下所示
須要先了解 DynamicContext
類的做用:主要用於記錄解析動態SQL語句以後產生的SQL語句片斷,能夠認爲它是一個用於記錄動態SQL語句解析結果的容器
抽象構件爲 SqlNode
接口,源碼以下
public interface SqlNode {
boolean apply(DynamicContext context);
}
複製代碼
apply
是 SQLNode
接口中定義的惟一方法,該方法會根據用戶傳入的實參,參數解析該SQLNode所記錄的動態SQL節點,並調用 DynamicContext.appendSql()
方法將解析後的SQL片斷追加到 DynamicContext.sqlBuilder
中保存,當SQL節點下全部的 SqlNode
完成解析後,咱們就能夠從 DynamicContext
中獲取一條動態生產的、完整的SQL語句
而後來看 MixedSqlNode
類的源碼
public class MixedSqlNode implements SqlNode {
private List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
for (SqlNode sqlNode : contents) {
sqlNode.apply(context);
}
return true;
}
}
複製代碼
MixedSqlNode
維護了一個 List<SqlNode>
類型的列表,用於存儲 SqlNode
對象,apply
方法經過 for循環
遍歷 contents 並調用其中對象的 apply
方法,這裏跟咱們的示例中的 Folder
類中的 print
方法很是相似,很明顯 MixedSqlNode
扮演了容器構件角色
對於其餘SqlNode子類的功能,稍微歸納以下:
TextSqlNode
:表示包含 ${}
佔位符的動態SQL節點,其 apply 方法會使用 GenericTokenParser
解析 ${}
佔位符,並直接替換成用戶給定的實際參數值IfSqlNode
:對應的是動態SQL節點 <If>
節點,其 apply 方法首先經過 ExpressionEvaluator.evaluateBoolean()
方法檢測其 test 表達式是否爲 true,而後根據 test 表達式的結果,決定是否執行其子節點的 apply() 方法TrimSqlNode
:會根據子節點的解析結果,添加或刪除相應的前綴或後綴。WhereSqlNode
和 SetSqlNode
都繼承了 TrimSqlNode
ForeachSqlNode
:對應 <foreach>
標籤,對集合進行迭代<choose>
、<when>
、<otherwise>
分別解析成 ChooseSqlNode
、IfSqlNode
、MixedSqlNode
綜上,SqlNode
接口有多個實現類,每一個實現類對應一個動態SQL節點,其中 SqlNode
扮演抽象構件角色,MixedSqlNode
扮演容器構件角色,其它通常是葉子構件角色
參考:
劉偉:設計模式Java版
慕課網java設計模式精講 Debug 方式+內存分析
Java AWT基礎及佈局管理
【java源碼一帶一路系列】之HashMap.putAll()
徐郡明:Mybatis技術內幕 3.2 SqlNode&SqlSource
Mybatis 3.4.7 文檔:動態 SQL