從接觸領域驅動設計的初學階段,到實現一個舊系統改造到DDD模型,再到按DDD規範落地的3個的項目。對於領域驅動模型設計研發,從開始的各種疑惑到吸收各種先進的理念,目前在技術實施這一塊已經基本比較成熟。在既往經驗中總結了一些在開發中遇到的技術問題和解決方案進行分享。 ...
基礎介紹:
組合模式用於表示部分-整體的層次結構。適用於希望用戶忽略組合對象與單個對象的不同,用戶將統一地使用組合結構中的所有對象的情況。
顧名思義,什麼叫部分-整體,比如常見的前端UI,一個DIV標簽中可以存在多個A標簽、P標簽、DIV標簽等等。
相較於DIV這個容器整體而言,其中所含的A標簽、P標簽甚至是DIV標簽都是單個的部分。
而顯示的時候卻是一視同仁,不分部分還是整體。
這就是典型的組合模式。
再比如WinForms應用程式中,Label、TextBox等這樣簡單的控制項,可以理解為節點對象,它們中無法再插入其他控制項,它們就是最小的。
而比如GroupBox、DataGrid這樣由多個簡單控制項組成的複合控制項或者容器,就可以理解為容器對象,它們中可以再插入其他的節點對象,甚至是再插入其他容器對象。
但不管是Label這種節點對象還是DataGrid這種容器對象,想要顯示的話都需要執行OnPaint方法。
為了表示這種對象之間整體與部分的層次結構,System.Windows.Forms.Control類就是應用了這種組合模式。
這樣就可以簡單的把組合模式分為三個部分:
- 抽象組件類(Component):它可以是介面或抽象類,為節點組件和容器組件對象聲明介面,在該類中包含共有行為的聲明。在抽象組件類中,定義了訪問及管理它的子組件的方法。
- 節點組件類(Leaf):節點對象為最小組件(可以理解為樹葉),並繼承自抽象組件類,實現其共有聲明和方法。
- 容器組件類(Composite):容器對象可以包含無數節點對象和無數容器組件(可以理解為樹枝,可以有無數樹葉或者分支),容器對象需要實現管理子對象的方法,如Add、Remove等。
應用場景:
當發現需求中是體現部分與整體層次的結構時,以及你希望用戶可以忽略組合對象與單個對象的不同,統一地使用組合結構中的所有對象時,就應該考慮使用組合模式了。
UI的一系列控制項就是使用了組合模式,整體和部分可以被一致對待。
組合模式有時候又叫做部分-整體模式,它使我們樹型結構的問題中,模糊了簡單元素和複雜元素的概念,客戶程式可以向處理簡單元素一樣來處理複雜元素,從而使得客戶程式與複雜元素的內部結構解耦。
以下情況下適用Composite模式:
1.對象的部分-整體層次結構。
2.忽略組合對象與單個對象的不同,統一地使用組合結構中的所有對象。
創建方式:
組合模式實現的最關鍵的地方是——簡單對象和複合對象必須實現相同的介面。這就是組合模式能夠將組合對象和簡單對象進行一致處理的原因。
組合模式有兩種實現方式,一種是:透明式的組合模式,另外一種是:安全式的組合模式。
透明方式————————————————
Leaf葉類中也有Add 與 Remove方法,這種方式叫透明方式。
也就是說在Component中聲明所有用來管理子對象的方法,其中包括Add、Remove等。
這樣實現Component介面的所有子類都具備了Add與Remove。
這樣做的好處是葉節點和枝節點對於外界沒有區別,它們具有一致的行為介面。
但問題也很明顯,因為Leaf類本身不具備Add、Remove方法的功能,其實現是沒有意義的。
安全方式————————————————
在Component介面中不去聲明Add與Remove方法,那麼子類Leaf也就不用必須實現它們,而在Composite類中聲明所有用來管理子類對象的方法。
以文檔管理器為例,文件夾為Composite,各類文檔為Leaf。
-
透明方式
1.抽象類
1 /// <summary> 2 /// 抽象組件類(Component) 3 /// </summary> 4 public abstract class DocumentComponent 5 { 6 public string Name { get; set; } 7 protected List<DocumentComponent> mChildren; 8 public List<DocumentComponent> Children 9 { 10 get { return mChildren; } 11 } 12 public DocumentComponent(string name) 13 { 14 this.Name = name; 15 mChildren = new List<DocumentComponent>(); 16 } 17 18 19 public abstract void AddChild(DocumentComponent document); 20 21 public abstract void RemoveChild(DocumentComponent document); 22 23 public abstract void Show(); 24 }
介面或抽象類,為節點組件和容器組件對象聲明介面,在該類中包含共有行為的聲明。
在抽象組件類中,定義了訪問及管理它的子組件的方法。
本實例中Show為節點和容器組件共有方法,AddChild和RemoveChild為容器組件方法。
本類主要是為了讓節點類和容器類進行繼承方便統一管理。
2.節點組件類
1 /// <summary> 2 /// 節點組件類(Leaf),各類文檔,每類型可以添加一個對應類。 3 /// </summary> 4 public sealed class Word : DocumentComponent 5 { 6 public Word(string name) 7 : base(name) 8 { } 9 public override void AddChild(DocumentComponent document) 10 { 11 throw new Exception("節點類不支持"); 12 } 13 14 public override void RemoveChild(DocumentComponent document) 15 { 16 throw new Exception("節點類不支持"); 17 } 18 19 public override void Show() 20 { 21 Console.WriteLine("這是一篇word文檔:" + Name); 22 } 23 }
節點對象為最小組件(可以理解為樹葉),並繼承自抽象組件類,實現show方法。
AddChild和RemoveChild為容器組件方法,在節點類中拋出異常即可。
該類是最小單位,沒有子節點。
本類一個word文檔對象,如果有多個類型的文檔,可以聲明多個類。
3.容器組件類
1 /// <summary> 2 /// 容器組件類(Composite),文件夾 3 /// </summary> 4 public class Folder : DocumentComponent 5 { 6 public Folder(string name) 7 : base(name) 8 { } 9 public override void AddChild(DocumentComponent document) 10 { 11 mChildren.Add(document); 12 Console.WriteLine("文檔或文件夾增加成功"); 13 } 14 public override void RemoveChild(DocumentComponent document) 15 { 16 mChildren.Remove(document); 17 Console.WriteLine("文檔或文件夾刪除成功"); 18 } 19 public override void Show() 20 { 21 Console.WriteLine("這是一個文件夾:" + Name); 22 } 23 }
容器對象可以包含無數節點對象和無數容器組件(可以理解為樹枝,可以有無數樹葉或者分支),容器對象需要實現管理子對象的方法,如AddChild、RemoveChild等。
本類是一個文件夾對象。
4.客戶端
1 /// <summary> 2 /// 客戶端 3 /// </summary> 4 class Client 5 { 6 /// <summary> 7 /// 廣度優先檢索 8 /// </summary> 9 /// <param name="component"></param> 10 private static void BreadthFirstSearch(DocumentComponent component) 11 { 12 Queue<DocumentComponent> q = new Queue<DocumentComponent>(); 13 q.Enqueue(component); 14 Console.WriteLine(component.Name); 15 while (q.Count > 0) 16 { 17 DocumentComponent temp = q.Dequeue(); 18 List<DocumentComponent> children = temp.Children; 19 foreach (DocumentComponent child in children) 20 { 21 Console.WriteLine(child.Name); 22 q.Enqueue(child); 23 } 24 } 25 } 26 27 /// <summary> 28 /// 深度優先檢索 29 /// </summary> 30 /// <param name="component"></param> 31 private static void DepthFirstSearch(DocumentComponent component) 32 { 33 Console.WriteLine(component.Name); 34 List<DocumentComponent> children = component.Children; 35 if (children == null || children.Count == 0) return; 36 foreach (DocumentComponent child in children) 37 { 38 DepthFirstSearch(child); 39 } 40 } 41 42 static void Main(string[] args) 43 { 44 Console.WriteLine("創建三個目錄:"); 45 Folder folder = new Folder("根目錄"); 46 Folder folder1 = new Folder("子目錄1"); 47 Folder folder2 = new Folder("子目錄2"); 48 49 Console.WriteLine("\r\n創建兩個文檔:"); 50 Word word1 = new Word("word文檔1"); 51 Word word2 = new Word("word文檔2"); 52 53 Console.WriteLine("\r\n將子目錄1添加到根目錄下:"); 54 folder.AddChild(folder1); 55 Console.WriteLine("\r\n將子目錄2添加到子目錄1下:"); 56 folder1.AddChild(folder2); 57 58 Console.WriteLine("\r\n將word文檔1添加到子目錄2下:"); 59 folder2.AddChild(word1); 60 Console.WriteLine("\r\n將word文檔2添加到根目錄下:"); 61 folder.AddChild(word2); 62 63 Console.WriteLine("\r\n廣度優先列表:"); 64 DepthFirstSearch(folder); 65 Console.WriteLine("\r\n深度優先列表:"); 66 BreadthFirstSearch(folder); 67 68 Console.ReadKey(); 69 } 70 71 72 }
註:BreadthFirstSearch為廣度優先檢索,依次列出所有元素。DepthFirstSearch為深度優先檢索,列舉完一個文件夾後,返回根目錄繼續列舉其他文件夾。
通過上述實例可以看出,文件夾可以創建N個子文件夾,但文檔只能放在文件夾中,無法放在另一個文檔中。
-
安全方式
1 /// <summary> 2 /// 抽象組件類(Component) 3 /// </summary> 4 public abstract class DocumentComponent 5 { 6 public string Name { get; set; } 7 protected List<DocumentComponent> mChildren; 8 public List<DocumentComponent> Children 9 { 10 get { return mChildren; } 11 } 12 public DocumentComponent(string name) 13 { 14 this.Name = name; 15 mChildren = new List<DocumentComponent>(); 16 } 17 18 public abstract void Show(); 19 } 20 21 /// <summary> 22 /// 節點組件類(Leaf),各類文檔,每類型可以添加一個對應類。 23 /// </summary> 24 public sealed class Word : DocumentComponent 25 { 26 public Word(string name) 27 : base(name) 28 { } 29 30 public override void Show() 31 { 32 Console.WriteLine("這是一篇word文檔:" + Name); 33 } 34 } 35 36 /// <summary> 37 /// 容器組件類(Composite),文件夾 38 /// </summary> 39 public class Folder : DocumentComponent 40 { 41 public Folder(string name) 42 : base(name) 43 { } 44 public void AddChild(DocumentComponent document) 45 { 46 mChildren.Add(document); 47 Console.WriteLine("文檔或文件夾增加成功"); 48 } 49 public void RemoveChild(DocumentComponent document) 50 { 51 mChildren.Remove(document); 52 Console.WriteLine("文檔或文件夾刪除成功"); 53 } 54 public override void Show() 55 { 56 Console.WriteLine("這是一個文件夾:" + Name); 57 } 58 } 59 60 61 /// <summary> 62 /// 客戶端 63 /// </summary> 64 class Client 65 { 66 /// <summary> 67 /// 廣度優先檢索 68 /// </summary> 69 /// <param name="component"></param> 70 private static void BreadthFirstSearch(DocumentComponent component) 71 { 72 Queue<DocumentComponent> q = new Queue<DocumentComponent>(); 73 q.Enqueue(component); 74 Console.WriteLine(component.Name); 75 while (q.Count > 0) 76 { 77 DocumentComponent temp = q.Dequeue(); 78 List<DocumentComponent> children = temp.Children; 79 foreach (DocumentComponent child in children) 80 { 81 Console.WriteLine(child.Name); 82 q.Enqueue(child); 83 } 84 } 85 } 86 87 /// <summary> 88 /// 深度優先檢索 89 /// </summary> 90 /// <param name="component"></param> 91 private static void DepthFirstSearch(DocumentComponent component) 92 { 93 Console.WriteLine(component.Name); 94 List<DocumentComponent> children = component.Children; 95 if (children == null || children.Count == 0) return; 96 foreach (DocumentComponent child in children) 97 { 98 DepthFirstSearch(child); 99 } 100 } 101 102 static void Main(string[] args) 103 { 104 Console.WriteLine("創建三個目錄:"); 105 Folder folder = new Folder("根目錄"); 106 Folder folder1 = new Folder("子目錄1"); 107 Folder folder2 = new Folder("子目錄2"); 108 109 Console.WriteLine("\r\n創建兩個文檔:"); 110 Word word1 = new Word("word文檔1"); 111 Word word2 = new Word("word文檔2"); 112 113 Console.WriteLine("\r\n將子目錄1添加到根目錄下:"); 114 folder.AddChild(folder1); 115 Console.WriteLine("\r\n將子目錄2添加到子目錄1下:"); 116 folder1.AddChild(folder2); 117 118 Console.WriteLine("\r\n將word文檔1添加到子目錄2下:"); 119 folder2.AddChild(word1); 120 Console.WriteLine("\r\n將word文檔2添加到根目錄下:"); 121 folder.AddChild(word2); 122 123 Console.WriteLine("\r\n廣度優先列表:"); 124 DepthFirstSearch(folder); 125 Console.WriteLine("\r\n深度優先列表:"); 126 BreadthFirstSearch(folder); 127 128 Console.ReadKey(); 129 } 130 131 }
從上述實例中可以看出,安全模式其實就是把共有的方法放在抽象類的。
文件夾獨有的方法放在容器類中,這樣做保證了節點類就沒有Add和Remove等無用方法。
總結:
組合模式解耦了客戶程式與複雜元素內部結構,從而使客戶程式可以向處理簡單元素一樣來處理複雜元素。
作者:少年真愛 出處:https://www.cnblogs.com/mingnianjiehunba/p/17735739.html 博主的文章沒有高度、深度和廣度,只是湊字數。由於博主的水平不高,不足和錯誤之處在所難免,希望大家能夠批評指出。 博主是利用讀書、參考、引用、抄襲、複製和粘貼等多種方式打造成自己的文章,請原諒博主成為一個無恥的文檔搬運工!