【軟件構造】第五章第三節 可復用的設計模式
第五章第三節 可復用的設計模式
除了Framework,5-2節所討論的其他技術都過於“基礎”和“細小”,有沒有辦法做更大規模的復用設計?
本節將介紹幾種典型的“面向復用”的設計模式,設計模式更強調多個類/對象之間的關系和交互過程—比接口/類復用的粒度更大。
Outline
- 結構型模式:Structural patterns
- 適配器模式(Adapter)
- 裝飾器模式(Decorator )
- 外觀模式(Facade)
- 行為類模式:Behavioral patterns
- 策略模式(Strategy)
- 模板方法模式(Template method)
- 叠代器模式( Iterator)
Notes
## 結構型模式:Structural patterns
【適配器模式(Adapter)】
- 目的:將某個類/接口轉換為用戶期望的其他形式。
- 含義:適配器模式是作為兩個互不相容的接口的橋梁,將某個類/接口轉換為client期望的其他形式。適配器模式使得原本由於接口不兼容而不能一起工作的的那些類/接口可以一起工作。
- 用途:主要解決在軟件系統中,需要將現存的類放到新的環境中,而環境要求的接口是現對象不能滿足的。
- 實現方法:通過增加一個新的接口,將已存在的子類封裝起來,client直接面向接口編程,從來隱藏了具體子類。適配器繼承或依賴已有的對象,實現想要的目標接口。
- 對象:將舊組件重用到新系統(也稱為“包裝器”)。
- 模型:
- 實例:
問題描述:其中LegacyRectangle是已有的類(需要傳入矩形的一個頂點、長和寬),但是與client要求的接口不一致(需要給出對角線兩個頂點坐標),我們此時建立一個新的接口Shape以供客戶端調用,用戶通過Shape接口傳入兩個點的坐標。Rectangle作為Adapter,實現該抽象接口,通過具體的方法實現適配。
在不適用適配器時:會發生委派不相容。
使用了適配器後就能夠解決上述問題:
【裝飾器模式(Decorator)】
- 含義:
- 裝飾器模式允許向一個現有的對象添加新的功能,同時又不改變其結構,它是作為一個現有的類的一個包裝。
- 這種模式創建了一個裝飾類,用來包裝原有的類,並在保證類方法簽名完整性的前提下,提供額外的功能。
- 裝飾模式是繼承的一個代替模式,裝飾模式可以動態地給一個對象添加一些額外的職能。就增加功能來說,裝飾器模式比生成子類更為靈活。
- 主要解決:一般的,我們為了擴展一個類經常使用繼承方式實現,由於繼承為類引入靜態特征,並且隨著擴展功能的增多,子類會很膨脹。
- 實現方法:將具體功能職責劃分,對每一個特性構造子類,通過委派機制增加到對象上。
- 用途:為對象增加不同側面的特性。
- 優點:裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴展一個實現類的功能。
- 缺點:多層裝飾比較復雜,客戶端需要一個具有多特性的object,需要一層一層的裝飾來實現
- 模型:
- 實例1:對於Shape接口和實現 參考:裝飾器模式 菜鳥教程
- 實例2:
- 我們需要對Stack數據結構的各種擴展:
- UndoStack:一個允許你撤銷先前的push或pop操作的棧
- SecureStack:一個需要密碼的棧
- SynchronizedStack:一個串行化並發訪問的棧
- 我們可以采用繼承的方式來解決。之後我們又需要任意可組合的擴展:
- SecureUndoStack:需要密碼的堆棧,並且還可以撤消以前的操作
- SynchronizedUndoStack:一個堆棧,用於序列化並發訪問,還可以讓您撤銷先前的操作
- SecureSynchronizedStack:需要密碼的堆棧,並用於序列化並發訪問的操作
- 我們又怎麽處理呢?繼承層次結構?多繼承?但是會顯得很麻煩,這時候裝飾器模式就可以很好地解決這一問題。
- 我們需要對Stack數據結構的各種擴展:
我們需要一層層具有多種特征的object,通過一層層的裝飾來實現:
Stack s = new ArrayStack(); //構建一個普通的堆棧 UndoStack s = new UndoStack(new ArrayStack()); //構建撤消堆棧 SecureStack s = new SecureStack( new SynchronizedStack( new UndoStack(s)))//構建安全的同步撤消堆棧
- 裝飾器 vs. 繼承
- 裝飾器在運行時組成特征;繼承在編譯時組成特征。
- 裝飾器由多個協作對象組成;繼承產生一個明確類型的對象。
- 可以混合和匹配多個裝飾;多重繼承在概念上是困難的。
java.util.Collections
中也有一些裝飾器模式:- 將mutable list 變為 immutable list:
static List<T> unmodifiableList(List<T> lst); static Set<T> unmodifiableSet( Set<T> set); static Map<K,V> unmodifiableMap( Map<K,V> map);
- Similar for synchronization:
static List<T> synchronizedList(List<T> lst); static Set<T> synchronizedSet( Set<T> set); static Map<K,V> synchronizedMap( Map<K,V> map);
【外觀模式(Facade Pattern)】
- 含義:外觀模式隱藏系統的復雜性,並向客戶端提供了一個客戶端可以訪問系統的接口。這種類型的設計模式屬於結構型模式,它向現有的系統添加一個接口,來隱藏系統的復雜性。這種模式涉及到一個單一的類,該類提供了客戶端請求的簡化方法和對現有系統類方法的委托調用。
- 意圖:為子系統中的一組接口提供一個一致的界面,外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。
- 主要解決:降低訪問復雜系統的內部子系統時的復雜度,簡化客戶端與之的接口。
- 實現方法:提供一個統一的接口來取代一系列小接口調用,相當於對復雜系統做了一個封裝,簡化客戶端使用。便於客戶端學習,解耦 。
- 用途:為了解決客戶端需要通過一個簡化的接口來訪問復雜系統內的功能這一問題提出的。
- 優點: 1、減少系統相互依賴。 2、提高靈活性。 3、提高了安全性。
- 缺點:不符合開閉原則,如果要改東西很麻煩,繼承重寫都不合適。
- 模式:
- 實例一:定義外觀類 ShapeMaker,並對其進行實現 參考 外觀模型 菜鳥教程
-
實例二:
假設我們有一個具有一組接口的應用程序來使用MySql / Oracle數據庫,並生成不同類型的報告,如HTML報告,PDF報告等。
因此,我們將有不同的接口集合來處理不同類型的數據庫。現在客戶端應用程序可以使用這些接口來獲取所需的數據庫連接並生成報告。但是,當復雜性增加或界面行為名稱混淆時,客戶端應用程序將難以管理它。
因此,我們可以在這裏應用Facade模式,並在現有界面的頂部提供包裝界面以幫助客戶端應用程序。
Two Helper Classes for MySQL and Oracle: 分別封裝了客戶端所需的功能
A fa?ade class:
客戶端代碼:
我們可以看到采用了Facade設計模式的客戶端代碼簡潔了許多,更方便客戶使用。
## 行為類模式:Behavioral patterns
【策略模式( Strategy)】
- 含義:在策略模式中,一個類的行為或算法可以在運行時更改。在策略模式中,我們創建表示各種模式的對象和一個行為隨著策略對象改變而改變的 context對象,策略對象改變 context對象的執行算法。
- 用途:針對特定任務存在不同的算法,但客戶端可以根據動態上下文在運行時切換算法。
- 實現方法:為算法創建一個接口,並為算法的每個變體創建一個實現類。
- 解決方法:將這些算法封裝成一個一個的類,任意地替換
- 優點:算法可以自由切換、避免使用多重條件判斷、擴展性良好。
- 缺點:策略類會增多、所有策略類都需要對外暴露。
- 模式:
- 實例一:Strategy接口與Contex實現 參考:策略模式 菜鳥驛站
- 實例二:對於Lab3中的內容進行抽象
- 問題描述:對於一個圖,我們可以計算圖中頂點的不同類型的中心度,如degreeCentrality(點度中心度)、closenessCentrality(接近中心度)和betweenCentrality(中介中心度)。
- 用Strategy原則:
- CentralityStrategy.java
public interface CentralityStrategy { //對應上圖的 Strategy<<interface>> public abstract centrality(); }
- 然後通過三個具體的實現類來實現這個接口:
- degreeCentralityStrategy.java
1 public class degreeCentralityStrategy<L extends Vertex, E extends Edge> implements CentralityStrategy { 2 3 private final Graph<L, E> g; 4 private final L v; 5 6 public degreeCentralityStrategy(Graph<L, E> g, L v) { 7 this.g = g; 8 this.v = v; 9 } 10 11 @Override 12 public double centrality() { 13 return GraphMetrics.degreeCentrality(g, v); 14 } 15 }
- closenessCentralityStrategy.java
1 public class closenessCentralityStrategy<L extends Vertex, E extends Edge> implements CentralityStrategy { 2 3 private final Graph<L, E> g; 4 private final L v; 5 6 public closenessCentralityStrategy(Graph<L, E> g, L v) { 7 this.g = g; 8 this.v = v; 9 } 10 11 @Override 12 public double centrality() { 13 return GraphMetrics.closenessCentrality(g, v); 14 } 15 }
- betweennessCentralityStrategy.java
1 public class betweennessCentralityStrategy<L extends Vertex, E extends Edge> implements CentralityStrategy{ 2 3 private final Graph<L, E> g; 4 private final L v; 5 6 public betweennessCentralityStrategy(Graph<L, E> g, L v) { 7 this.g = g; 8 this.v = v; 9 } 10 11 @Override 12 public double centrality() { 13 return GraphMetrics.betweennessCentrality(g, v); 14 } 15 }
- 然後實現一個Context類 centralityContext.java
public class CentralityContext { public double centrality(CentralityStrategy centralityType) { return centralityType.centrality(); } }
- 可以使用Context來查看當它改變策咯Strategy時的行為變化,如下Main.java
1 public class Main { 2 3 public static void main(String[] args) { 4 5 Graph<Vertex, Edge> graph = GraphFactory.createGraph("test/graph/GraphPoetTest.txt"); 6 String[] strings = {"F", "24"}; 7 Vertex vertex1 = VertexFactory.createVertex("to", "Word", strings); 8 CentralityContext context = new CentralityContext(); 9 double degree = context.centrality(new DegreeCentralityStrategy<>(graph, vertex1)); 10 double closeness = context.centrality(new ClosenessCentralityStrategy<>(graph, vertex1)); 11 double betweenness = context.centrality(new BetweennessCentralityStrategy<>(graph, vertex1)); 12 System.out.println(degree); 13 System.out.println(closeness); 14 System.out.println(betweenness); 15 } 16 }
- 用Strategy原則:
【模板模式(Template method)】
- 含義:在模板模式中,一個抽象類公開定義了執行它的方法的方式/模式,它的子類可以按照需要重寫實現方法,但調用將以抽象類中定義的方法進行。
- 意圖:定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。
- 實現方法:共性的步驟在抽象類內公共實現,差異化的步驟在各個子類中實現。一般使用繼承和重寫實現模板模式。
- 優點:封裝不變部分,擴展可變部分;提取公共代碼,便於維護;行為由父類控制嗎,子類實現
- 缺點:每一個不同的實現都需要一個子類來實現,導致類的個數增加,使得系統更加龐大。
- 模式:
- 實例一:Game抽象類和重寫 參考 模板模式 菜鳥教程
- 實例二:對於Lab3中的內容進行抽象
- 問題描述:邊有很多類型:簡單的分為:有向邊、無向邊
- Edge.java //創建擴展了上述類的實現類
1 public abstract class Edge { 2 3 private final String label; 4 private final double weight; 5 6 //the constructor 7 public Edge(String label, double weight) { 8 this.label = label; 9 this.weight = weight; 10 } 11 12 public abstract boolean addVertices(List<Vertex> vertices); 13 14 public abstract boolean containVertex(Vertex v); 15 16 public abstract Set<Vertex> vertices();
- DirectedEdge.java
1 public class DirectedEdge extends Edge{ 2 3 private Vertex source; 4 private Vertex target; 5 6 //the constructor 7 public DirectedEdge(String label, double weight) { 8 super(label, weight); 9 } 10 11 @Override 12 public boolean addVertices(List<Vertex> vertices) { 13 source = vertices.get(0); 14 target = vertices.get(1); 15 return true; 16 } 17 18 @Override 19 public boolean containVertex(Vertex v) { 20 return source.equals(v) || target.equals(v); 21 } 22 23 @Override 24 public Set<Vertex> vertices() { 25 Set<Vertex> set = new HashSet<Vertex>(); 26 set.add(source); 27 set.add(target); 28 return set; 29 }
- UndirectedEdge.java
1 public class UndirectedEdge extends Edge{ 2 3 private Vertex vertex1; 4 private Vertex vertex2; 5 6 public UndirectedEdge(String label, double weight) { 7 super(label, weight); 8 } 9 10 @Override 11 public boolean addVertices(List<Vertex> vertices) { 12 vertex1 = vertices.get(0); 13 vertex2 = vertices.get(1); 14 return true; 15 } 16 17 @Override 18 public boolean containVertex(Vertex v) { 19 return vertex1.equals(v) || vertex2.equals(v); 20 } 21 22 @Override 23 public Set<Vertex> vertices() { 24 Set<Vertex> set = new HashSet<Vertex>(); 25 set.add(vertex1); 26 set.add(vertex2); 27 return set; 28 }
- Edge.java //創建擴展了上述類的實現類
【叠代器模式( Iterator)】
- 含義:這種模式用於順序訪問集合對象的元素,而又無需暴露該對象的內部表示。
- 用途:解決 客戶需要統一的策略來訪問容器中的所有元素,與容器類型無關
- 實現方法:這種模式讓自己的集合類實現
Iterable
接口,並實現自己的獨特Iterator
叠代器(hasNext, next, remove
),允許客戶端利用這 個叠代器進行顯式或隱式的叠代遍歷。 - 優點: 1、它支持以不同的方式遍歷一個聚合對象。 2、叠代器簡化了聚合類。 3、在同一個聚合上可以有多個遍歷。 4、在叠代器模式中,增加新的聚合類和叠代器類都很方便,無須修改原有代碼。
- 缺點:由於叠代器模式將存儲數據和遍歷數據的職責分離,增加新的聚合類需要對應增加新的叠代器類,類的個數成對增加,這在一定程度上增加了系統的復雜性。
- 模式:
- 實例:
Iterable
接口:實現該接口的集合對象是可叠代遍歷的public interface Iterable<T> { ... Iterator<T> iterator(); }
Iterator
接口:叠代器public interface Iterator<E> { boolean hasNext(); E next(); void remove(); }
- 具體例子見下圖:
【軟件構造】第五章第三節 可復用的設計模式