目錄
UML類圖
設計模式六大原則
1.單一職責原則
2.里氏替換原則
3.依賴倒置原則
4.接口隔離原則
5.迪米特法則(最少知道原則)
6.開(放封)閉原則
設計模式分類
1.創建型模式
2.結構型模式
4.行為型模式
一、工廠模式(factory——簡單工廠模式和抽象工廠模式)
1.1、簡單工廠模式(simple factory)
主要解決的問題
何時使用
如何解決
應用實例
優點
缺點
結構
示例
未使用設計模式的代碼
使用設計模式的代碼
?1.2、抽象工廠模式(abstract factory)
主要解決
適用場景
解決方案
關鍵代碼
實例
優點
缺點
使用場景
注意事項
結構
實現
二、策略模式(strategy factory)
主要解決的問題
使用場景
應用實例
優點
缺點
使用建議
注意事項
示例
未使用設計模式的代碼
使用設計模式的代碼
三、裝飾器模式(decorator factory)
主要解決的問題
使用場景
應用實例
優點
缺點
使用建議
注意事項
示例
未使用設計模式代碼
使用設計模式代碼
四、代理模式(proxy factory)
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
示例
未使用設計模式的代碼
使用設計模式代碼
五、工廠方法模式(factory function)
六、模板模式(template)
介紹
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
實現
七、外觀模式(decorator)
介紹
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
結構
實現
八、建造者模式(builder)
概要
意圖
主要解決
何時使用
如何解決
關鍵代碼
應用實例
優點
缺點
使用場景
注意事項
對比
實現
九、觀察者模式(observer)
介紹
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
結構
實現
十、命令模式(command)
介紹
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
結構
十一、原型模式(prototype)
概要
原型模式
何時使用
如何解決
關鍵代碼
應用實例
優點
缺點
使用場景
注意事項
結構
實現實例
十二、狀態模式(state)
介紹
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
結構
實現
十三、適配器模式(adapter)
概述
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
結構
實現
十四、備忘錄模式(memento)
介紹
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
結構
實現
十五、組合模式(component)
介紹
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
結構
實現
十六、迭代器模式(Iterator )
介紹
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
結構
實現
十七、單例模式(Singleton )
概要
單例模式(Singleton Pattern)
意圖
主要解決
何時使用
如何解決
關鍵代碼
應用實例
優點
缺點
使用場景
注意事項
結構
實現
單例模式的幾種實現方式
1、懶漢式,線程不安全
2、懶漢式,線程安全
2、餓漢式
4、雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)
十八、橋接模式(bridge)
介紹
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
結構
實現
十九、責任鏈模式(Chain of Responsibility)
介紹
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
結構
實現
二十、中介者模式
介紹
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
結構
實現
二十一、享元模式
概要
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
結構
實現
二十二、解釋器模式
介紹
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
結構
實現
二十三、訪問者模式
介紹
意圖
主要解決的問題
使用場景
實現方式
關鍵代碼
應用實例
優點
缺點
使用建議
注意事項
包含的幾個主要角色
實現
參考文獻
UML類圖
虛線+箭頭:依賴關系,比如動物依賴氧氣和水。 實線+箭頭:關聯關系,比如企鵝關聯到環境氣候。 實線+三角空心箭頭:繼承關系,比如大雁、企鵝都繼承鳥。 虛線+三角空心箭頭:接口實現關系,比如大雁實現了飛翔接口 實心棱形+箭頭:組合關系(也叫合成關系),每只鳥都有兩只翅膀,鳥與翅膀誰也離不開誰,鳥與翅膀的關系就叫做組合 。 空心棱形+箭頭:聚合關系,每一只大雁都有自己的雁群,每個雁群都有好多大雁。
設計模式六大原則
1.單一職責原則
原則思想:一個方法只負責一件事情。
描述:單一職責原則很簡單,一個方法 一個類只負責一個職責,各個職責的程序改動,不影響其它程序。 這是常識,幾乎所有程序員都會遵循這個原則。
優點:降低類和類的耦合,提高可讀性,增加可維護性和可拓展性,降低可變性的風險。
2.里氏替換原則
原則思想:使用的基類可以在任何地方使用繼承的子類,完美的替換基類。
描述:子類可以擴展父類的功能,但不能改變父類原有的功能。子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法,子類中可以增加自己特有的方法。 優點:增加程序的健壯性,即使增加了子類,原有的子類還可以繼續運行,互不影響。
3.依賴倒置原則
原則思想:高層次的模塊不應該依賴于低層次的模塊,他們都應該依賴于抽象,抽象不應該依賴于具體實現,具體實現應該依賴于抽象。
描述:類A直接依賴類B,假如要將類A改為依賴類C,則必須通過修改類A的代碼來達成。這種場景下,類A一般是高層模塊,負責復雜的業務邏輯;類B和類C是低層模塊,負責基本的原子操作;假如修改類A,會給程序帶來不必要的風險。
優點:可以減少需求變化帶來的工作量,做并行開發更加友好。
4.接口隔離原則
原則思想:類和類之間應該建立在最小接口的上。
描述:類A通過接口依賴B,類C通過接口依賴D,如果接口類A和類B不是最小的接口,則依賴的類B和類D必須要實現他們不需要的方法。
優點:提高程序的靈活度,提高內聚,減少對外交互,使得最小的接口做最多的事情。
5.迪米特法則(最少知道原則)
原則思想:一個對象應當對其他對象有盡可能少地了解,簡稱類間解耦。
描述:一個類盡量減少自己對其他對象的依賴,原則是低耦合,高內聚,只有使各個模塊之間的耦合盡量的低,才能提高代碼的復用率。
優點:低耦合,高內聚。
6.開(放封)閉原則
原則思想:盡量通過擴展軟件實體來解決需求變化,而不是通過修改已有的代碼來完成變化。
描述:一個軟件產品在生命周期內,都會發生變化,既然變化是一個既定的事實,我們就應該在設計的時候盡量適應這些變化,以提高項目的穩定性和靈活性。
優點:單一原則告訴我們,每個類都有自己負責的職責,里氏替換原則不能破壞繼承關系的體系。
設計模式分類
1.創建型模式
單例模式,工廠模式,抽象工廠模式或簡單工廠模式,建造者模式,原型模式。
2.結構型模式
適配器模式,橋接模式,裝飾模式,組合模式,外觀模式,享元模式,代理模式。
4.行為型模式
模版模式,命令模式,迭代器模式,觀察者模式,中介者模式,備忘錄模式,解釋器模式,狀態模式,策略模式,職責鏈模式,訪問者模式。
一、工廠模式(factory——簡單工廠模式和抽象工廠模式)
1.1、簡單工廠模式(simple factory)
工廠模式提供了一種創建對象的方式,而無需指定要創建的具體類。
通過使用簡單工廠模式,可以將對象的創建邏輯封裝在一個工廠類中,而不是在客戶端代碼中直接實例化對象,這樣可以提高代碼的可維護性和可擴展性。
主要解決的問題
接口選擇的問題。
何時使用
當我們需要在不同條件下創建不同實例時。
如何解決
通過讓子類實現工廠接口,返回一個抽象的產品。
應用實例
-
汽車制造:你需要一輛汽車,只需從工廠提貨,而不需要關心汽車的制造過程及其內部實現。
-
Hibernate:更換數據庫時,只需更改方言(Dialect)和數據庫驅動(Driver),即可實現對不同數據庫的切換。
優點
-
調用者只需要知道對象的名稱即可創建對象。
-
擴展性高,如果需要增加新產品,只需擴展一個工廠類即可。
-
屏蔽了產品的具體實現,調用者只關心產品的接口。
缺點
每次增加一個產品時,都需要增加一個具體類和修改對應的工廠,使系統中類的數量成倍增加,增加了系統的復雜度和具體類的依賴。
結構
工廠模式包含以下幾個主要角色:
-
抽象產品(Abstract Product):定義了產品的共同接口或抽象類。它可以是具體產品類的父類或接口,規定了產品對象的共同方法。
-
具體產品(Concrete Product):實現了抽象產品接口,定義了具體產品的特定行為和屬性。
-
具體工廠(Concrete Factory):實現了抽象工廠接口,負責實際創建具體產品的對象。
示例
實現一個加減乘除的計算器
未使用設計模式的代碼
public class Operation {
?public static double GetResult(double numberA, double numberB, String operate){switch(operate){case "+":return numberA + numberB;case "-":return numberA - numberB;case "*":return numberA * numberB;case "/":return numberA / numberB;default:throw new IllegalStateException("未識別的符號");}}
}
public class Invoker {public static void main(String[] args) {double a = 2;double b = 3;System.out.println("加:" + Operation.GetResult(a, b, "+"));System.out.println("減:" + Operation.GetResult(a, b, "-"));System.out.println("乘:" + Operation.GetResult(a, b, "*"));System.out.println("除:" + Operation.GetResult(a, b, "/"));}
}
//輸出結果: 加:5.0 減:-1.0 乘:6.0 除:0.6666666666666666 ?
使用設計模式的代碼
定義基礎類(將要操作的兩個數抽出來)和基礎操作方法
import lombok.Data;
?
@Data
public abstract class Operation {
?double a;
?double b;
?public abstract double getResult();
}
定義
public class OperationAdd extends Operation{
?@Overridepublic double getResult() {return getA() + getB();}
?
}
public class OperationSubtract extends Operation{
?@Overridepublic double getResult() {return getA() - getB();}
?
}
public class OperationMulti extends Operation{
?@Overridepublic double getResult() {return getA() * getB();}
?
}
public class OperationDiv extends Operation{
?@Overridepublic double getResult() {if(getNumberB() == 0){throw new IllegalStateException("除數不能為0");}return getA() / getB();}
?
}
public class OperationFactory {public static Operation createOperation(String operate){switch (operate){case "+":return new OperationAdd();case "-":return new OperationSubtract();case "*":return new OperationMulti();case "/":return new OperationDiv();default:throw new IllegalStateException("未識別的符號");}}
}
調用者客戶端:
public class Invoker {public static void main(String[] args) {double a = 2;double b = 3;
?Operation operationAdd = OperationFactory.createOperation("+");operationAdd.setA(a);operationAdd.setB(b);System.out.println("加:" + operationAdd.getResult());
?Operation operationSubtract = OperationFactory.createOperation("-");operationSubtract.setA(a);operationSubtract.setB(b);System.out.println("減:" + operationSubtract.getResult());
?Operation operationMulti = OperationFactory.createOperation("*");operationMulti.setA(a);operationMulti.setB(b);System.out.println("乘:" + operationMulti.getResult());
?Operation operationDiv = OperationFactory.createOperation("/");operationDiv.setA(a);operationDiv.setB(b);System.out.println("除:" + operationDiv.getResult());}
}
//輸出結果 加:5.0 減:-1.0 乘:6.0 除:0.6666666666666666
?1.2、抽象工廠模式(abstract factory)
抽象工廠模式(Abstract Factory Pattern)是圍繞一個超級工廠創建其他工廠。該超級工廠又稱為其他工廠的工廠。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。
在抽象工廠模式中,接口是負責創建一個相關對象的工廠,不需要顯式指定它們的類。每個生成的工廠都能按照工廠模式提供對象。
抽象工廠模式提供了一種創建一系列相關或相互依賴對象的接口,而無需指定具體實現類。通過使用抽象工廠模式,可以將客戶端與具體產品的創建過程解耦,使得客戶端可以通過工廠接口來創建一族產品。
主要解決
接口選擇的問題。
適用場景
當系統需要創建多個相關或依賴的對象,而不需要指定具體類時。
解決方案
在一個產品族中定義多個產品,由具體工廠實現創建這些產品的方法。
關鍵代碼
在一個工廠中聚合多個同類產品的創建方法。
實例
假設有不同類型的衣柜,每個衣柜(具體工廠)只能存放一類衣服(成套的具體產品),如商務裝、時尚裝等。每套衣服包括具體的上衣和褲子(具體產品)。所有衣柜都是衣柜類(抽象工廠)的具體實現,所有上衣和褲子分別實現上衣接口和褲子接口(抽象產品)。
優點
-
確保同一產品族的對象一起工作。
-
客戶端不需要知道每個對象的具體類,簡化了代碼。
缺點
擴展產品族非常困難。增加一個新的產品族需要修改抽象工廠和所有具體工廠的代碼。
使用場景
-
QQ 換皮膚時,整套皮膚一起更換。
-
創建跨平臺應用時,生成不同操作系統的程序。
注意事項
增加新的產品族相對容易,而增加新的產品等級結構比較困難。
結構
抽象工廠模式包含以下幾個主要角色:
-
抽象工廠(Abstract Factory):聲明了一組用于創建產品對象的方法,每個方法對應一種產品類型。抽象工廠可以是接口或抽象類。
-
具體工廠(Concrete Factory):實現了抽象工廠接口,負責創建具體產品對象的實例。
-
抽象產品(Abstract Product):定義了一組產品對象的共同接口或抽象類,描述了產品對象的公共方法。
-
具體產品(Concrete Product):實現了抽象產品接口,定義了具體產品的特定行為和屬性。
抽象工廠模式通常涉及一族相關的產品,每個具體工廠類負責創建該族中的具體產品。客戶端通過使用抽象工廠接口來創建產品對象,而不需要直接使用具體產品的實現類。
實現
package com.example.designmodestudy.abstractfunction.mode;
?
public interface Color {void fill();
}
?
package com.example.designmodestudy.abstractfunction.mode;
?
public class Blue implements Color {
?@Overridepublic void fill() {System.out.println("藍色");}
}
?
package com.example.designmodestudy.abstractfunction.mode;
public class Green implements Color {
?@Overridepublic void fill() {System.out.println("綠色");}
}
?
?
package com.example.designmodestudy.abstractfunction.mode;
?
public class Red implements Color {
?@Overridepublic void fill() {System.out.println("紅色");}
}
?
package com.example.designmodestudy.abstractfunction.mode;
?
public interface Shape {void draw();
}
?
?
package com.example.designmodestudy.abstractfunction.mode;
?
public class Circle implements Shape {
?@Overridepublic void draw() {System.out.println("圓形");}
}
?
?
package com.example.designmodestudy.abstractfunction.mode;
?
public class Rectangle implements Shape {
?@Overridepublic void draw() {System.out.println("長方形.");}
}
?
?
?
package com.example.designmodestudy.abstractfunction.mode;
?
public class Square implements Shape {
?@Overridepublic void draw() {System.out.println("正方形");}
}
?
?
package com.example.designmodestudy.abstractfunction.factory;
?
import com.example.designmodestudy.abstractfunction.mode.Color;
import com.example.designmodestudy.abstractfunction.mode.Shape;
?
public abstract class AbstractFactory {public abstract Color getColor(String color);public abstract Shape getShape(String shape);
}
?
?
package com.example.designmodestudy.abstractfunction.factory;
?
import com.example.designmodestudy.abstractfunction.mode.*;
?
public class ColorFactory extends AbstractFactory {
?@Overridepublic Shape getShape(String shapeType){return null;}
?@Overridepublic Color getColor(String color) {if(color == null){return null;}if(color.equalsIgnoreCase("RED")){return new Red();} else if(color.equalsIgnoreCase("GREEN")){return new Green();} else if(color.equalsIgnoreCase("BLUE")){return new Blue();}return null;}
}
?
?
package com.example.designmodestudy.abstractfunction.factory;
?
import com.example.designmodestudy.abstractfunction.mode.*;
?
public class ShapeFactory extends AbstractFactory {
?@Overridepublic Shape getShape(String shapeType){if(shapeType == null){return null;}if(shapeType.equalsIgnoreCase("CIRCLE")){return new Circle();} else if(shapeType.equalsIgnoreCase("RECTANGLE")){return new Rectangle();} else if(shapeType.equalsIgnoreCase("SQUARE")){return new Square();}return null;}
?@Overridepublic Color getColor(String color) {return null;}
}
?
?
package com.example.designmodestudy.abstractfunction.factory;
?
public class FactoryProducer {public static AbstractFactory getFactory(String choice){if(choice.equalsIgnoreCase("SHAPE")){return new ShapeFactory();} else if(choice.equalsIgnoreCase("COLOR")){return new ColorFactory();}return null;}
}
?
package com.example.designmodestudy.abstractfunction;
?
import com.example.designmodestudy.abstractfunction.factory.AbstractFactory;
import com.example.designmodestudy.abstractfunction.factory.FactoryProducer;
import com.example.designmodestudy.abstractfunction.mode.Color;
import com.example.designmodestudy.abstractfunction.mode.Shape;
?
public class FactoryPatternDemo {public static void main(String[] args) {
?//獲取形狀工廠AbstractFactory shapeFactory = FactoryProducer.getFactory("SHAPE");
?//獲取形狀為 Circle 的對象Shape shape1 = shapeFactory.getShape("CIRCLE");
?//調用 Circle 的 draw 方法shape1.draw();
?//獲取形狀為 Rectangle 的對象Shape shape2 = shapeFactory.getShape("RECTANGLE");
?//調用 Rectangle 的 draw 方法shape2.draw();
?//獲取形狀為 Square 的對象Shape shape3 = shapeFactory.getShape("SQUARE");
?//調用 Square 的 draw 方法shape3.draw();
?//獲取顏色工廠AbstractFactory colorFactory = FactoryProducer.getFactory("COLOR");
?//獲取顏色為 Red 的對象Color color1 = colorFactory.getColor("RED");
?//調用 Red 的 fill 方法color1.fill();
?//獲取顏色為 Green 的對象Color color2 = colorFactory.getColor("GREEN");
?//調用 Green 的 fill 方法color2.fill();
?//獲取顏色為 Blue 的對象Color color3 = colorFactory.getColor("BLUE");
?//調用 Blue 的 fill 方法color3.fill();}
}
?
//結果:
圓形 長方形. 正方形 紅色 綠色 藍色
二、策略模式(strategy factory)
在策略模式(Strategy Pattern)中一個類的行為或其算法可以在運行時更改。這種類型的設計模式屬于行為型模式。
在策略模式定義了一系列算法或策略,并將每個算法封裝在獨立的類中,使得它們可以互相替換。通過使用策略模式,可以在運行時根據需要選擇不同的算法,而不需要修改客戶端代碼。
在策略模式中,我們創建表示各種策略的對象和一個行為隨著策略對象改變而改變的 context 對象。策略對象改變 context 對象的執行算法。
主要解決的問題
-
解決在多種相似算法存在時,使用條件語句(如if...else)導致的復雜性和難以維護的問題。
使用場景
-
當一個系統中有許多類,它們之間的區別僅在于它們的行為時。
應用實例
-
錦囊妙計:每個錦囊代表一個策略,包含不同的計策。
-
旅行方式選擇:騎自行車、坐汽車等,每種方式都是一個可替換的策略。
-
Java AWT的LayoutManager:不同的布局管理器實現了相同的接口,但提供了不同的布局算法。
優點
-
算法切換自由:可以在運行時根據需要切換算法。
-
避免多重條件判斷:消除了復雜的條件語句。
-
擴展性好:新增算法只需新增一個策略類,無需修改現有代碼。
缺點
-
策略類數量增多:每增加一個算法,就需要增加一個策略類。
-
所有策略類都需要暴露:策略類需要對外公開,以便可以被選擇和使用。
-
在使用時,需要知道有哪些策略,要使用那種策略。
使用建議
-
當系統中有多種算法或行為,且它們之間可以相互替換時,使用策略模式。
-
當系統需要動態選擇算法時,策略模式是一個合適的選擇。
注意事項
-
如果系統中策略類數量過多,考慮使用其他模式或設計技巧來解決類膨脹問題。
示例
實現一個滿足促銷的收費計算功能。
未使用設計模式的代碼
public class DiscountOperate {public static double operate(double price, int number, ?String operate){switch (operate){case "正常收費":return price * number;case "打八折":return price * number * 0.8;case "打七折":return price * number * 0.7;case "打五折":return price * number * 0.5;case "返利5元":return price * number - 5;default:throw new IllegalStateException("未識別的折扣");}}
}
調用者客戶端:
public class Invoker {public static void main(String[] args) {double price = 10.5;int number = 5;
?String normalOperate = "正常收費";System.out.println(normalOperate + ":" + DiscountOperate.operate(price, number, normalOperate));String eightOperate = "打八折";System.out.println(eightOperate + ":" + DiscountOperate.operate(price, number, eightOperate));String sevenOperate = "打七折";System.out.println(sevenOperate + ":" + DiscountOperate.operate(price, number, sevenOperate));String rebateOperate = "返利5元";System.out.println(rebateOperate + ":" + DiscountOperate.operate(price, number, rebateOperate));}
}
//結果輸出: 正常收費:52.5 打八折:42.0 打七折:36.75 返利5元:47.5
使用設計模式的代碼
@Data
public abstract class CollectFee {
?private double price;private int number;
?//算法方法abstract double operate();
}
public class NormalCollectFee extends CollectFee{@Overridepublic double operate() {return getPrice() * getNumber();}
}
public class EightCollectFee extends CollectFee{@Overridepublic double operate() {return getPrice() * getNumber() * 0.8;}
}
public class SevenCollectFee extends CollectFee{@Overridepublic double operate() {return getPrice() * getNumber() * 0.5;}
}
public class RebateCollectFee extends CollectFee{@Overridepublic double operate() {return getPrice() * getNumber() - 5;}
}
public class Context {
?private CollectFee collectFee;
?//初始化時賦值對應的策略對象public Context(CollectFee collectFee){this.collectFee = collectFee;}
?//上下文接口方法public double contextInterface(){return collectFee.operate();}
?
}
調用者客戶端:
public class Invoker {public static void main(String[] args) {int number = 5;double price = 10.5;NormalCollectFee collectFee = new NormalCollectFee();collectFee.setPrice(price);collectFee.setNumber(number);Context normalContext = new Context(collectFee);System.out.println("正常收費:" + normalContext.contextInterface());Context eightContext = new Context(collectFee);System.out.println("打八折收費:" + eightContext.contextInterface());Context sevenContext = new Context(collectFee);System.out.println("打七折收費:" + sevenContext.contextInterface());Context rebateContext = new Context(collectFee);System.out.println("返利5元收費:" + rebateContext.contextInterface());}
}
//輸出結果: 正常收費:52.5 打八折收費:42.0 打七折收費:26.25 返利5元收費:47.5
三、裝飾器模式(decorator factory)
主要解決的問題
-
避免通過繼承引入靜態特征,特別是在子類數量急劇膨脹的情況下。
-
允許在運行時動態地添加或修改對象的功能。
使用場景
-
當需要在不增加大量子類的情況下擴展類的功能。
-
當需要動態地添加或撤銷對象的功能。
應用實例
-
孫悟空的72變:孫悟空(ConcreteComponent)通過變化(Decorator)獲得新的能力。
-
畫框裝飾畫:一幅畫(ConcreteComponent)可以通過添加玻璃(ConcreteDecorator)和畫框(ConcreteDecorator)來增強其展示效果。
優點
-
低耦合:裝飾類和被裝飾類可以獨立變化,互不影響。
-
靈活性:可以動態地添加或撤銷功能。
-
替代繼承:提供了一種繼承之外的擴展對象功能的方式。
缺點
-
復雜性:多層裝飾可能導致系統復雜性增加。
使用建議
-
在需要動態擴展功能時,考慮使用裝飾器模式。
-
保持裝飾者和具體組件的接口一致,以確保靈活性。
注意事項
-
裝飾器模式可以替代繼承,但應謹慎使用,避免過度裝飾導致系統復雜。
示例
實現一個人可以選擇性的裝扮的功能。
未使用設計模式代碼
import lombok.AllArgsConstructor;
import lombok.Data;
?
@AllArgsConstructor
@Data
public class Person {
?private String name;
?//形象展示public void show(){System.out.println("裝扮的" + name + "。");}
}
/*** 服飾類*/
public abstract class Finery {
?public abstract void show();
}
public class BigTrouser extends Finery{@Overridepublic void show() {System.out.println("垮褲");;}
}
public class LeatherShoes extends Finery{@Overridepublic void show() {System.out.println("皮鞋");;}
}
public class Sneakers extends Finery{@Overridepublic void show() {System.out.println("破球鞋");;}
}
public class Suit extends Finery{@Overridepublic void show() {System.out.println("西裝");;}
}
public class Tie extends Finery{@Overridepublic void show() {System.out.println("領帶");;}
}
public class TShirts extends Finery{@Overridepublic void show() {System.out.println("大T恤");;}
}
客戶端調用:
public class Invoker {
?public static void main(String[] args) {Person person = new Person("程序員");System.out.println("========失業的裝扮=========");Finery dtx = new TShirts();Finery kk = new BigTrouser();Finery pqx = new Sneakers();dtx.show();kk.show();pqx.show();person.show();
?System.out.println("=======上班的裝扮========");Finery sz = new Suit();Finery ld = new Tie();Finery px = new LeatherShoes();sz.show();ld.show();px.show();person.show();}
}
//輸出結果: ========失業的裝扮========= 大T恤 垮褲 破球鞋 裝扮的程序員。 ========上班的裝扮========= 西裝 領帶 皮鞋 裝扮的程序員。
使用設計模式代碼
裝飾器模式模型:
本次業務演變模型:
import lombok.Data;
?
@Data
public class Person {
?private String name;
?public Person(){}public Person(String name){this.name = name;}
?public void show(){System.out.println("裝扮的" + name + "。");}
?
}
/*** 服飾類*/
public class Finery extends Person {
?protected Person component;
?public void finery(Person component){this.component = component;}
?@Overridepublic void show(){component.show();}
}
public class BigTrouser extends Finery{
?@Overridepublic void show() {System.out.println("垮褲");super.show();}
}
public class LeatherShoes extends Finery{
?@Overridepublic void show() {System.out.println("皮鞋");super.show();}
}
public class Sneakers extends Finery{
?@Overridepublic void show() {System.out.println("破球鞋");super.show();}
}
public class Suit extends Finery{
?@Overridepublic void show() {System.out.println("西裝");super.show();}
}
public class Tie extends Finery{
?@Overridepublic void show() {System.out.println("領帶");super.show();}
}
public class TShirts extends Finery{
?@Overridepublic void show() {System.out.println("大T恤");super.show();}
}
調用者客戶端:
public class Invoker {
?public static void main(String[] args) {Person person = new Person("程序員");System.out.println("========失業的裝扮=========");Sneakers pqx = new Sneakers();BigTrouser kk = new BigTrouser();TShirts dtx = new TShirts();pqx.finery(person);kk.finery(pqx);dtx.finery(kk);dtx.show();
?System.out.println("========上班的裝扮=========");LeatherShoes px = new LeatherShoes();Tie ld = new Tie();Suit sz = new Suit();px.finery(person);ld.finery(px);sz.finery(ld);sz.show();}
}
//輸出結果 ========失業的裝扮========= 大T恤 垮褲 破球鞋 裝扮的程序員。 ========上班的裝扮========= 西裝 領帶 皮鞋 裝扮的程序員。
四、代理模式(proxy factory)
主要解決的問題
-
代理模式解決的是在直接訪問某些對象時可能遇到的問題,例如對象創建成本高、需要安全控制或遠程訪問等。
使用場景
-
當需要在訪問一個對象時進行一些控制或額外處理時。
實現方式
-
增加中間層:創建一個代理類,作為真實對象的中間層。
-
代理與真實對象組合:代理類持有真實對象的引用,并在訪問時進行控制。
關鍵代碼
-
代理類:實現與真實對象相同的接口,并添加額外的控制邏輯。
-
真實對象:實際執行任務的對象。
應用實例
-
快捷方式:Windows系統中的快捷方式作為文件或程序的代理。
-
角色扮演:孫悟空作為高翠蘭的代理,豬八戒無法區分。
-
代售點:購買火車票時,代售點作為火車站的代理。
-
支票:作為銀行賬戶資金的代理,控制資金的訪問。
-
Spring AOP:使用代理模式來實現面向切面編程。
優點
-
職責分離:代理模式將訪問控制與業務邏輯分離。
-
擴展性:可以靈活地添加額外的功能或控制。
-
智能化:可以智能地處理訪問請求,如延遲加載、緩存等。
缺點
-
性能開銷:增加了代理層可能會影響請求的處理速度。
-
實現復雜性:某些類型的代理模式實現起來可能較為復雜。
示例
實現一個窈窕淑女,君子好逑的功能(男生買各種東西追求女生),男孩因為害羞不敢直接送禮物給女孩,因此找中間人向女孩送禮物。
未使用設計模式的代碼
import lombok.AllArgsConstructor;
import lombok.Data;
?
@AllArgsConstructor
@Data
public class Girl {private String name;
?
}
public class Boy{
?Girl girl;
?public Boy(Girl girl){this.girl = girl;System.out.println("我喜歡你, " + girl.getName() + "!");}
?public void giveDolls(){System.out.println("送洋娃娃");}
?public void giveFlowers(){System.out.println("送鮮花");}
?public void giveChocolate(){System.out.println("送巧克力");}
}
public class MiddlePerson extends Boy {public MiddlePerson(Girl girl) {super(girl);System.out.println("(中間人代送)");}
}
客戶端調用者:
public class Invoker {
?public static void main(String[] args) {Girl girl = new Girl("小喬");MiddlePerson middlePerson = new MiddlePerson(girl);middlePerson.giveDolls();middlePerson.giveFlowers();middlePerson.giveChocolate();}
}
//結果輸出: 我喜歡你, 小喬! (中間人代送) 送洋娃娃 送鮮花 送巧克力
使用設計模式代碼
代理模式原始模型:
本次業務的演變模型:
import lombok.AllArgsConstructor;
import lombok.Data;
?
@AllArgsConstructor
@Data
public class Girl {private String name;
?
}
public interface GiveGift {
?void giveDolls();
?void giveFlowers();
?void giveChocolate();
}
public class Boy implements GiveGift{
?Girl girl;
?public Boy(Girl girl){this.girl = girl;System.out.println("我喜歡你, " + girl.getName() + "!");}
?@Overridepublic void giveDolls(){System.out.println("送洋娃娃");}
?@Overridepublic void giveFlowers(){System.out.println("送鮮花");}
?@Overridepublic void giveChocolate(){System.out.println("送巧克力");}
}
public class Proxy implements GiveGift{
?Boy boy;
?public Proxy(Girl girl){boy = new Boy(girl);System.out.println("(中間人代送)");}
?//功能擴展public void introduction(){System.out.println("自我介紹:我家有50套房、10輛豪車、1個億的存款。");}
?@Overridepublic void giveDolls(){System.out.println("送洋娃娃");}
?@Overridepublic void giveFlowers(){System.out.println("送鮮花");}
?@Overridepublic void giveChocolate(){System.out.println("送巧克力");}
}
客戶端調用者
public class Invoker {
?public static void main(String[] args) {Girl girl = new Girl("小喬");
// ? ? ? Boy proxy = new Boy(girl);Proxy proxy = new Proxy(girl);proxy.giveDolls();proxy.giveFlowers();proxy.giveChocolate();}
}
//輸出結果
我喜歡你, 小喬! (中間人代送)
自我介紹:我家有50套房、10輛豪車、1個億的存款。
送洋娃娃
送鮮花
送巧克力
五、工廠方法模式(factory function)
- 封裝性:將對象的創建過程封裝在工廠類中,客戶端代碼通過工廠類來創建對象,而無需知道具體的創建邏輯。
- 解耦:實現了客戶端代碼與具體產品類的解耦,客戶端只需關心產品的接口,而無需關心產品的具體實現。
- 可擴展性:當需要添加新的產品時,只需擴展工廠類即可,無需修改客戶端代碼。
package com.example.designmodestudy.factoryfunction;
?
import lombok.Data;
?
@Data
public abstract class Operation {
?double a;
?double b;
?public abstract double getResult();
}
?
package com.example.designmodestudy.factoryfunction;
?
public class OperationAdd extends Operation {
?@Overridepublic double getResult() {return getA() + getB();}
?
}
?
?
package com.example.designmodestudy.factoryfunction;
?
public class OperationDiv extends Operation {
?@Overridepublic double getResult() {if(getB() == 0){throw new IllegalStateException("除數不能為0");}return getA() / getB();}
?
}
?
?
package com.example.designmodestudy.factoryfunction;
?
public class OperationMulti extends Operation {
?@Overridepublic double getResult() {return getA() * getB();}
?
}
?
?
package com.example.designmodestudy.factoryfunction;
?
public class OperationSubtract extends Operation {
?@Overridepublic double getResult() {return getA() - getB();}
?
}
?package com.example.designmodestudy.factoryfunction.factory;
?
import com.example.designmodestudy.factoryfunction.Operation;
?
public interface OperationFactory {
?Operation createOperation();
}
?
?
package com.example.designmodestudy.factoryfunction.factory;
?
import com.example.designmodestudy.factoryfunction.Operation;
import com.example.designmodestudy.factoryfunction.OperationAdd;
?
public class AddOperationFactory implements OperationFactory {@Overridepublic Operation createOperation() {return new OperationAdd();}
}
?
?
package com.example.designmodestudy.factoryfunction.factory;
?
import com.example.designmodestudy.factoryfunction.Operation;
import com.example.designmodestudy.factoryfunction.OperationDiv;
?
public class DivOperationFactory implements OperationFactory {@Overridepublic Operation createOperation() {return new OperationDiv();}
}
?
?
package com.example.designmodestudy.factoryfunction.factory;
?
import com.example.designmodestudy.factoryfunction.Operation;
import com.example.designmodestudy.factoryfunction.OperationMulti;
?
public class MultiOperationFactory implements OperationFactory {@Overridepublic Operation createOperation() {return new OperationMulti();}
}
?
?
package com.example.designmodestudy.factoryfunction.factory;
?
import com.example.designmodestudy.factoryfunction.Operation;
import com.example.designmodestudy.factoryfunction.OperationSubtract;
?
public class SubtractOperationFactory implements OperationFactory {@Overridepublic Operation createOperation() {return new OperationSubtract();}
}
?package com.example.designmodestudy.factoryfunction;
?
import com.example.designmodestudy.factoryfunction.factory.*;
?
public class Invoker {public static void main(String[] args) {double a = 2;double b = 3;
?OperationFactory addOperationFactory = new AddOperationFactory();com.example.designmodestudy.factoryfunction.Operation operationAdd = addOperationFactory.createOperation();operationAdd.setA(a);operationAdd.setB(b);System.out.println("加:" + operationAdd.getResult());
?OperationFactory subtractOperationFactory = new SubtractOperationFactory();com.example.designmodestudy.factoryfunction.Operation operationSubtract = subtractOperationFactory.createOperation();operationSubtract.setA(a);operationSubtract.setB(b);System.out.println("減:" + operationSubtract.getResult());
?OperationFactory multiOperationFactory = new MultiOperationFactory();com.example.designmodestudy.factoryfunction.Operation operationMulti = multiOperationFactory.createOperation();operationMulti.setA(a);operationMulti.setB(b);System.out.println("乘:" + operationMulti.getResult());
?OperationFactory divOperationFactory = new DivOperationFactory();Operation operationDiv = divOperationFactory.createOperation();operationDiv.setA(a);operationDiv.setB(b);System.out.println("除:" + operationDiv.getResult());}
}
//結果:
加:5.0 減:-1.0 乘:6.0 除:0.6666666666666666
六、模板模式(template)
在模板模式(Template Pattern)中,一個抽象類公開定義了執行它的方法的方式/模板。它的子類可以按需要重寫方法實現,但調用將以抽象類中定義的方式進行。這種類型的設計模式屬于行為型模式。
介紹
意圖
在父類中定義了算法的骨架,并允許子類在不改變算法結構的前提下重定義算法的某些特定步驟。
主要解決的問題
-
解決在多個子類中重復實現相同的方法的問題,通過將通用方法抽象到父類中來避免代碼重復。
使用場景
-
當存在一些通用的方法,可以在多個子類中共用時。
實現方式
-
定義抽象父類:包含模板方法和一些抽象方法或具體方法。
-
實現子類:繼承抽象父類并實現抽象方法,不改變算法結構。
關鍵代碼
-
模板方法:在抽象父類中定義,調用抽象方法和具體方法。
-
抽象方法:由子類實現,代表算法的可變部分。
-
具體方法:在抽象父類中實現,代表算法的不變部分。
應用實例
-
建筑流程:地基、走線、水管等步驟相同,后期建筑如加壁櫥、柵欄等步驟不同。
-
西游記的81難:菩薩定好的81難代表一個頂層邏輯骨架。
-
Spring對Hibernate的支持:封裝了如開啟事務、獲取Session、關閉Session等通用方法。
優點
-
封裝不變部分:算法的不變部分被封裝在父類中。
-
擴展可變部分:子類可以擴展或修改算法的可變部分。
-
提取公共代碼:減少代碼重復,便于維護。
缺點
-
類數目增加:每個不同的實現都需要一個子類,可能導致系統龐大。
使用建議
-
當有多個子類共有的方法且邏輯相同時,考慮使用模板方法模式。
-
對于重要或復雜的方法,可以考慮作為模板方法定義在父類中。
注意事項
-
為了防止惡意修改,模板方法通常使用
final
關鍵字修飾,避免被子類重寫。
實現
我們將創建一個定義操作的 TestPaper 抽象類,其中,模板方法設置為 final,這樣它就不會被重寫。TestPaperA 和 TestPaperB 是擴展了 TestPaper 的實體類,它們重寫了抽象類的方法。
Invoker,是我們的演示類使用 TestPaper 來演示模板模式的用法。
package designmode.template;
?
/*** packageName designmode.template** @author 青藤* @version JDK 11* @className Template (此處以class為例)* @date 2025/4/21 16:05* @description TODO*/
public abstract class TestPaper {
?abstract void question1();
?abstract void question2();
?abstract String answer1();
?abstract String answer2();
?
?//模板方法public final void test() {question1();answer1();
?question2();answer2();}
}
?
package designmode.template;
?
/*** packageName designmode.template** @author 青藤* @version JDK 11* @className TestPaperA (此處以class為例)* @date 2025/4/21 16:15* @description TODO*/
public class TestPaperA extends TestPaper{@Overridevoid question1() {System.out.println("題1. 1+1=? A.1 B.2 C.3 D.4");System.out.println("答案:" + answer1());}
?@Overridevoid question2() {System.out.println("提2. 1+2=? A.1 B.2 C.3 D.4");System.out.println("答案:" + answer2());}
?@OverrideString answer1() {return "B";}
?@OverrideString answer2() {return "C";}
}
?
package designmode.template;
?
/*** packageName designmode.template** @author 青藤* @version JDK 11* @className TestPaperA (此處以class為例)* @date 2025/4/21 16:15* @description TODO*/
public class TestPaperB extends TestPaper{@Overridevoid question1() {System.out.println("題1. 2+1=? A.1 B.2 C.3 D.4");System.out.println("答案:" + answer1());}
?@Overridevoid question2() {System.out.println("題2. 2+2=? A.1 B.2 C.3 D.4");System.out.println("答案:" + answer2());}
?@OverrideString answer1() {return "C";}
?@OverrideString answer2() {return "D";}
}
?
package designmode.template;/*** packageName designmode.template** @author 青藤* @version JDK 11* @className Invoker (此處以class為例)* @date 2025/4/21 16:18* @description TODO*/
public class Invoker {public static void main(String[] args) {System.out.println("=====A卷考試======");TestPaper testPaperA = new TestPaperA();testPaperA.test();System.out.println("=====B卷考試======");TestPaper testPaperB = new TestPaperB();testPaperB.test();}
}
//結果: 題1. 1+1=? A.1 B.2 C.3 D.4 答案:B 提2. 1+2=? A.1 B.2 C.3 D.4 答案:C =====B卷考試====== 題1. 2+1=? A.1 B.2 C.3 D.4 答案:C 題2. 2+2=? A.1 B.2 C.3 D.4 答案:D
七、外觀模式(decorator)
外觀模式(Facade Pattern)隱藏系統的復雜性,并向客戶端提供了一個客戶端可以訪問系統的接口。這種類型的設計模式屬于結構型模式,它向現有的系統添加一個接口,來隱藏系統的復雜性。
這種模式涉及到一個單一的類,該類提供了客戶端請求的簡化方法和對現有系統類方法的委托調用。
介紹
意圖
為一個復雜的子系統提供一個一致的高層接口。這樣,客戶端代碼就可以通過這個簡化的接口與子系統交互,而不需要了解子系統內部的復雜性。
主要解決的問題
-
降低客戶端與復雜子系統之間的耦合度。
-
簡化客戶端對復雜系統的操作,隱藏內部實現細節。
使用場景
-
當客戶端不需要了解系統內部的復雜邏輯和組件交互時。
-
當需要為整個系統定義一個清晰的入口點時。
實現方式
-
創建外觀類:定義一個類(外觀),作為客戶端與子系統之間的中介。
-
封裝子系統操作:外觀類將復雜的子系統操作封裝成簡單的方法。
關鍵代碼
-
Facade類:提供高層接口,簡化客戶端與子系統的交互。
-
子系統類:實現具體的業務邏輯,被Facade類調用。
應用實例
-
醫院接待:醫院的接待人員簡化了掛號、門診、劃價、取藥等復雜流程。
-
Java三層架構:通過外觀模式,可以簡化對表示層、業務邏輯層和數據訪問層的訪問。
優點
-
減少依賴:客戶端與子系統之間的依賴減少。
-
提高靈活性:子系統的內部變化不會影響客戶端。
-
增強安全性:隱藏了子系統的內部實現,只暴露必要的操作。
缺點
-
違反開閉原則:對子系統的修改可能需要對外觀類進行相應的修改。
使用建議
-
在需要簡化復雜系統訪問時使用外觀模式。
-
確保外觀類提供的方法足夠簡單,以便于客戶端使用。
注意事項
-
外觀模式適用于層次化結構,可以為每一層提供一個清晰的入口。
-
避免過度使用外觀模式,以免隱藏過多的細節,導致維護困難。
結構
外觀模式涉及以下核心角色:
-
外觀(Facade):
-
提供一個簡化的接口,封裝了系統的復雜性。外觀模式的客戶端通過與外觀對象交互,而無需直接與系統的各個組件打交道。
-
-
子系統(Subsystem):
-
由多個相互關聯的類組成,負責系統的具體功能。外觀對象通過調用這些子系統來完成客戶端的請求。
-
-
客戶端(Client):
-
使用外觀對象來與系統交互,而不需要了解系統內部的具體實現。
-
實現
我們將創建一個 Shape 接口和實現了 Shape 接口的實體類。下一步是定義一個外觀類 ShapeMaker。
ShapeMaker 類使用實體類來代表用戶對這些類的調用。FacadePatternDemo 類使用 ShapeMaker 類來顯示結果。
package designmode.facade;
?
public interface Shape {
?void draw();
}
?
package designmode.facade;
?
public class Circle ?implements Shape{@Overridepublic void draw() {System.out.println("畫了個圓");}
}
?
package designmode.facade;
?
public class Rectangle implements Shape{@Overridepublic void draw() {System.out.println("畫了個長方形");}
}
?
package designmode.facade;
?
public class Square implements Shape{@Overridepublic void draw() {System.out.println("畫了個正方形");}
}
?
package designmode.facade;
?
public class ShapeMarker {
?private Shape circle;
?private Shape rectangle;
?private Shape square;
?public ShapeMarker(){circle = new Circle();rectangle = new Rectangle();square = new Square();}
?public void drawCircle(){circle.draw();}
?public void drawRectangle(){rectangle.draw();}
?public void drawSquare(){square.draw();}
}
?
package designmode.facade;
?
public class Invoker {
?public static void main(String[] args) {ShapeMarker shapeMarker = new ShapeMarker();shapeMarker.drawCircle();shapeMarker.drawRectangle();shapeMarker.drawSquare();}
}
?
//結果:
畫了個圓 畫了個長方形 畫了個正方形
八、建造者模式(builder)
建造者模式是一種創建型設計模式,它允許你創建復雜對象的步驟與表示方式相分離。
建造者模式是一種創建型設計模式,它的主要目的是將一個復雜對象的構建過程與其表示相分離,從而可以創建具有不同表示形式的對象。
概要
意圖
將一個復雜的構建過程與其表示相分離,使得同樣的構建過程可以創建不同的表示。
主要解決
在軟件系統中,一個復雜對象的創建通常由多個部分組成,這些部分的組合經常變化,但組合的算法相對穩定。
何時使用
當一些基本部件不變,而其組合經常變化時。
如何解決
將變與不變的部分分離開。
關鍵代碼
-
建造者:創建并提供實例。
-
導演:管理建造出來的實例的依賴關系和控制構建過程。
應用實例
-
去肯德基,漢堡、可樂、薯條、炸雞翅等是不變的,而其組合是經常變化的,生成出不同的"套餐"。
-
Java 中的
StringBuilder
。
優點
-
分離構建過程和表示,使得構建過程更加靈活,可以構建不同的表示。
-
可以更好地控制構建過程,隱藏具體構建細節。
-
代碼復用性高,可以在不同的構建過程中重復使用相同的建造者。
缺點
-
如果產品的屬性較少,建造者模式可能會導致代碼冗余。
-
增加了系統的類和對象數量。
使用場景
-
需要生成的對象具有復雜的內部結構。
-
需要生成的對象內部屬性相互依賴。
注意事項
與工廠模式的區別是:建造者模式更加關注于零件裝配的順序。
對比
1、工廠方法模式VS建造者模式 工廠方法模式注重的是整體對象的創建方式;而建造者模式注重的是部件構建的過程,意在通過一步一步地精確構造創建出一個復雜的對象。
我們舉個簡單例子來說明兩者的差異,如要制造一個超人,如果使用工廠方法模式,直接產生出來的就是一個力大無窮、能夠飛翔、內褲外穿的超人;而如果使用建造者模式,則需要組裝手、頭、腳、軀干等部分,然后再把內褲外穿,于是一個超人就誕生了。
如果用工廠方法建造手機的話,直接就可以得到一個完整的手機,而用建造者模式,則需要按照指定順序組裝各個零件,才能得到一部手機。
2、抽象工廠模式VS建造者模式 抽象工廠模式實現對產品家族的創建,一個產品家族是這樣的一系列產品:具有不同分類維度的產品組合,采用抽象工廠模式則是不需要關心構建過程,只關心什么產品由什么工廠生產即可。
建造者模式則是要求按照指定的藍圖建造產品,它的主要目的是通過組裝零配件而產生一個新產品。
如果將抽象工廠模式看成汽車配件生產工廠,生產一個產品族的產品,那么建造者模式就是一個汽車組裝工廠,通過對部件的組裝可以返回一輛完整的汽車。
實現
以生產手機為例:
Builder(抽象建造者):定義手機需要那些配件 具體建造者:定義不同手機配件的生產標準 Director(指揮者):定義手機需要組裝什么配件,按照什么順序進行組裝。
package designmode.builder;
?
public abstract class Builder {
?protected Phone phone = new Phone();
?public abstract void buildCpu();
?public abstract void buildMemory();
?public abstract void buildColor();
?public Phone builder() {return phone;}
}
?
package designmode.builder;
?
import lombok.Data;
?
@Data
public class Phone {
?private String cpu;
?private String memory;
?private String color;
}
?
package designmode.builder;
?
public class XiaoMi extends Builder{@Overridepublic void buildCpu() {phone.setCpu("天機");}
?@Overridepublic void buildMemory() {phone.setMemory("512");}
?@Overridepublic void buildColor() {phone.setColor("白色");}
}
?
package designmode.builder;
?
public class HuaWei extends Builder{@Overridepublic void buildCpu() {phone.setCpu("麒麟");}
?@Overridepublic void buildMemory() {phone.setMemory("512");}
?@Overridepublic void buildColor() {phone.setColor("黑色");}
}
?
package designmode.builder;import lombok.Data;@Data
public class Director {private Builder builder;public Phone createPhone(){builder.buildCpu();builder.buildMemory();builder.buildColor();return builder.builder();}}
package designmode.builder;
?
public class Invoker {public static void main(String[] args) {Director director = new Director();director.setBuilder(new XiaoMi());Phone xiaomi = director.createPhone();System.out.println("小米手機:" + xiaomi);
?director.setBuilder(new HuaWei());Phone huawei = director.createPhone();System.out.println("華為手機:" + huawei);}
}
?
結果:
小米手機:Phone(cpu=天機, memory=512, color=白色) 華為手機:Phone(cpu=麒麟, memory=512, color=黑色)
九、觀察者模式(observer)
觀察者模式是一種行為型設計模式,它定義了一種一對多的依賴關系,當一個對象的狀態發生改變時,其所有依賴者都會收到通知并自動更新。
當對象間存在一對多關系時,則使用觀察者模式(Observer Pattern)。比如,當一個對象被修改時,則會自動通知依賴它的對象。觀察者模式屬于行為型模式。
介紹
意圖
創建了對象間的一種一對多的依賴關系,當一個對象狀態改變時,所有依賴于它的對象都會得到通知并自動更新。
主要解決的問題
-
觀察者模式解決的是一個對象狀態改變時,如何自動通知其他依賴對象的問題,同時保持對象間的低耦合和高協作性。
使用場景
-
當一個對象的狀態變化需要同時更新其他對象時。
實現方式
-
定義觀察者接口:包含一個更新方法。
-
創建具體觀察者:實現觀察者接口,定義接收到通知時的行為。
-
定義主題接口:包含添加、刪除和通知觀察者的方法。
-
創建具體主題:實現主題接口,管理觀察者列表,并在狀態改變時通知它們。
關鍵代碼
-
觀察者列表:在主題中維護一個觀察者列表。
應用實例
-
拍賣系統:拍賣師作為主題,競價者作為觀察者,拍賣價格更新時通知所有競價者。
-
西游記故事:菩薩灑水作為狀態改變,老烏龜作為觀察者,觀察到這一變化。
優點
-
抽象耦合:觀察者和主題之間是抽象耦合的。
-
觸發機制:建立了一套狀態改變時的觸發和通知機制。
缺點
-
性能問題:如果觀察者眾多,通知過程可能耗時。
-
循環依賴:可能導致循環調用和系統崩潰。
-
缺乏變化詳情:觀察者不知道主題如何變化,只知道變化發生。
使用建議
-
在需要降低對象間耦合度,并且對象狀態變化需要觸發其他對象變化時使用。
-
考慮使用Java內置的觀察者模式支持類,如
java.util.Observable
和java.util.Observer
。
注意事項
-
避免循環引用:注意觀察者和主題之間的依賴關系,避免循環引用。
-
異步執行:考慮使用異步通知避免單點故障導致整個系統卡殼。
結構
觀察者模式包含以下幾個核心角色:
-
主題(Subject):也稱為被觀察者或可觀察者,它是具有狀態的對象,并維護著一個觀察者列表。主題提供了添加、刪除和通知觀察者的方法。
-
觀察者(Observer):觀察者是接收主題通知的對象。觀察者需要實現一個更新方法,當收到主題的通知時,調用該方法進行更新操作。
-
具體主題(Concrete Subject):具體主題是主題的具體實現類。它維護著觀察者列表,并在狀態發生改變時通知觀察者。
-
具體觀察者(Concrete Observer):具體觀察者是觀察者的具體實現類。它實現了更新方法,定義了在收到主題通知時需要執行的具體操作。
觀察者模式通過將主題和觀察者解耦,實現了對象之間的松耦合。當主題的狀態發生改變時,所有依賴于它的觀察者都會收到通知并進行相應的更新。
實現
觀察者模式使用三個類 Subject、Observer 和 Client。Subject 對象帶有綁定觀察者到 Client 對象和從 Client 對象解綁觀察者的方法。我們創建 Subject 類、Observer 抽象類和擴展了抽象類 Observer 的實體類。
ObserverPatternDemo,我們的演示類使用 Subject 和實體類對象來演示觀察者模式。
package designmode.observer;
?
/*** 觀察者*/
public abstract class Observer {
?protected Subject subject;
?public abstract void update();
}
package designmode.observer;
?
/*** 二進制觀察者*/
public class BinaryObserver extends Observer{
?public BinaryObserver(Subject subject) {this.subject = subject;this.subject.addObserver(this);}
?@Overridepublic void update() {System.out.println("二進制:" + Integer.toBinaryString(subject.getState()));}
}
?
package designmode.observer;
?
/*** 十進制觀察者*/
public class OctalObserver extends Observer{
?public OctalObserver(Subject subject) {this.subject = subject;this.subject.addObserver(this);}
?@Overridepublic void update() {System.out.println("十進制:" + Integer.toOctalString(subject.getState()));}
}
?
?
package designmode.observer;
?
/*** 16進制觀察者*/
public class HexaObserver extends Observer{
?public HexaObserver(Subject subject) {this.subject = subject;this.subject.addObserver(this);}
?@Overridepublic void update() {System.out.println("十六進制:" + Integer.toHexString(subject.getState()).toUpperCase());}
}
package designmode.observer;
?
import lombok.Data;
?
import java.util.ArrayList;
import java.util.List;
?
/*** 主題*/
@Data
public class Subject {
?private List<Observer> observers = new ArrayList<Observer>();private int state;
?public void addObserver(Observer observer){observers.add(observer);}
?public void notifyAllObservers(){for (Observer observer : observers) {observer.update();}}
}
package designmode.observer;
?
/*** 調用類*/
public class Invoker {public static void main(String[] args) {Subject subject = new Subject();
?new BinaryObserver(subject);new OctalObserver(subject);new HexaObserver(subject);
?int state = 15;System.out.println("=======數字:" + state);subject.setState(state);subject.notifyAllObservers();
?state = 10;System.out.println("=======數字:" + state);subject.setState(state);subject.notifyAllObservers();}
}
?
結果:
=======數字:15 二進制:1111 十進制:17 十六進制:F =======數字:10 二進制:1010 十進制:12 十六進制:A
十、命令模式(command)
命令模式(Command Pattern)是一種數據驅動的設計模式,它屬于行為型模式。
命令模式將一個請求封裝為一個對象,從而使你可以用不同的請求對客戶進行參數化,對請求排隊或記錄請求日志,以及支持可撤銷的操作。
命令模式結構示意圖:
介紹
意圖
將請求封裝為一個對象,允許用戶使用不同的請求對客戶端進行參數化。
主要解決的問題
-
解決在軟件系統中請求者和執行者之間的緊耦合問題,特別是在需要對行為進行記錄、撤銷/重做或事務處理等場景。
使用場景
-
當需要對行為進行記錄、撤銷/重做或事務處理時,使用命令模式來解耦請求者和執行者。
實現方式
-
定義命令接口:所有命令必須實現的接口。
-
創建具體命令:實現命令接口的具體類,包含執行請求的方法。
-
調用者:持有命令對象并觸發命令的執行。
-
接收者:實際執行命令的對象。
關鍵代碼
-
接收者(Receiver):執行命令的實際對象。
-
命令(Command):定義執行命令的接口。
-
調用者(Invoker):使用命令對象的入口點。
應用實例
-
Struts 1:ActionServlet作為Invoker,模型層的類作為具體的Command。
優點
-
降低耦合度:請求者和執行者之間的耦合度降低。
-
易于擴展:新命令可以很容易地添加到系統中。
缺點
-
過多命令類:系統可能會有過多的具體命令類,增加系統的復雜度。
使用建議
-
在GUI中,每個按鈕或菜單項可以視為一條命令。
-
在需要模擬命令行操作的場景中使用命令模式。
注意事項
-
如果系統需要支持命令的撤銷(Undo)和恢復(Redo)操作,命令模式是一個合適的選擇。
結構
主要涉及到以下幾個核心角色:
-
命令(Command):
-
定義了執行操作的接口,通常包含一個
execute
方法,用于調用具體的操作。
-
-
具體命令(ConcreteCommand):
-
實現了命令接口,負責執行具體的操作。它通常包含了對接收者的引用,通過調用接收者的方法來完成請求的處理。
-
-
接收者(Receiver):
-
知道如何執行與請求相關的操作,實際執行命令的對象。
-
-
調用者/請求者(Invoker):
-
發送命令的對象,它包含了一個命令對象并能觸發命令的執行。調用者并不直接處理請求,而是通過將請求傳遞給命令對象來實現。
-
-
客戶端(Client):
-
創建具體命令對象并設置其接收者,將命令對象交給調用者執行。
-
package designmode.command;
?
/*** 命令接口*/
public interface Command {void execute();void undo();
}
?
package designmode.command;
?
/*** 具體命令類*/
public class ConcreteCommand implements Command{
?private TextEditor textEditor;
?private String text;
?public ConcreteCommand(TextEditor textEditor, String text) {this.textEditor = textEditor;this.text = text;}
?@Overridepublic void execute() {textEditor.setText(textEditor.getText() + text);}
?@Overridepublic void undo() {int index = textEditor.getText().lastIndexOf(text);if (index >= 0){textEditor.setText(textEditor.getText().substring(0, index));}}
}
?
package designmode.command;
?
import lombok.Data;
?
/*** 文本類*/
@Data
public class TextEditor {
?private String text = "Hello";
}
?
?
package designmode.command;
?
import lombok.Data;
?
/*** 命令執行器*/
@Data
public class CommandExecutor {
?private Command command;
?public void executeCommand(Command command) {command.execute();}
}
?
package designmode.command;/*** 調用者*/
public class Invoker {public static void main(String[] args) {TextEditor textEditor = new TextEditor();CommandExecutor commandExecutor = new CommandExecutor();System.out.println("=====添加單詞=======");Command command = new ConcreteCommand(textEditor, " World");commandExecutor.setCommand(command);commandExecutor.executeCommand(command);System.out.println(textEditor.getText());//執行撤銷操作System.out.println("======撤銷添加=========");command.undo();System.out.println(textEditor.getText());}
}
結果: =====添加單詞======= Hello World ======撤銷添加========= Hello
十一、原型模式(prototype)
原型模式(Prototype Pattern)是用于創建重復的對象,同時又能保證性能。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式之一。
這種模式是實現了一個原型接口,該接口用于創建當前對象的克隆。當直接創建對象的代價比較大時,則采用這種模式。例如,一個對象需要在一個高代價的數據庫操作之后被創建。我們可以緩存該對象,在下一個請求時返回它的克隆,在需要的時候更新數據庫,以此來減少數據庫調用。
概要
原型模式
意圖:使用原型實例指定要創建對象的種類,并通過拷貝這些原型創建新的對象。
主要解決:在運行時動態建立和刪除原型。
何時使用
-
系統應獨立于產品的創建、構成和表示。
-
需要在運行時指定實例化的類,例如通過動態加載。
-
避免創建與產品類層次平行的工廠類層次。
-
類的實例只能有幾種不同狀態組合,克隆原型比手工實例化更方便。
如何解決
通過已有的一個原型對象,快速生成與原型對象相同的實例。
關鍵代碼
-
實現克隆操作:
-
在 Java 中,實現
Cloneable
接口,重寫clone()
方法。 -
在 .NET 中,使用
Object
類的MemberwiseClone()
方法實現淺拷貝,或通過序列化實現深拷貝。
-
-
隔離類對象的使用者和具體類型之間的耦合關系,要求"易變類"擁有穩定的接口。
應用實例
-
細胞分裂
-
Java 中的
Object.clone()
方法
優點
-
性能提高
-
避免構造函數的約束
缺點
-
配備克隆方法需要全面考慮類的功能,對已有類可能較難實現,特別是處理不支持串行化的間接對象或含有循環結構的引用時。
-
必須實現
Cloneable
接口。
使用場景
-
資源優化
-
類初始化需要消耗大量資源(如數據、硬件資源)
-
性能和安全要求高的場景
-
通過
new
創建對象需要復雜的數據準備或訪問權限時 -
一個對象需要多個修改者
-
對象需提供給其他對象訪問并可能被各個調用者修改時
-
通常與工廠方法模式一起使用,通過
clone
創建對象,然后由工廠方法提供給調用者
注意事項
與直接實例化類創建新對象不同,原型模式通過拷貝現有對象生成新對象。淺拷貝通過實現 Cloneable
實現,深拷貝通過實現 Serializable
讀取二進制流實現。
結構
原型模式包含以下幾個主要角色:
-
原型接口(Prototype Interface):定義一個用于克隆自身的接口,通常包括一個
clone()
方法。 -
具體原型類(Concrete Prototype):實現原型接口的具體類,負責實際的克隆操作。這個類需要實現
clone()
方法,通常使用淺拷貝或深拷貝來復制自身。 -
客戶端(Client):使用原型實例來創建新的對象。客戶端調用原型對象的
clone()
方法來創建新的對象,而不是直接使用構造函數。
深拷貝和淺拷貝的概念:
- 淺拷貝(Shallow Copy):創建一個新對象,新對象的基本數據類型成員的值會被復制,而引用數據類型成員仍然指向原始對象所指向的內存地址。也就是說,淺拷貝只是復制了對象的“頂層”,對于引用類型的成員,實際上復制的是引用,而不是引用所指向的對象。
- 深拷貝(Deep Copy):創建一個新對象,不僅復制基本數據類型成員的值,對于引用數據類型的成員,也會為其創建新的對象,并復制其內容,使得新對象和原始對象完全獨立,修改其中一個對象的引用類型成員不會影響到另一個對象。
實現實例
實現一個簡歷信息部分復制的功能。
package designmode.prototype;
?
import lombok.Data;
?
/*** 簡歷類*/
@Data
public class Resume implements Cloneable {
?private String name;
?private String sex;
?private String age;
?private String timeArea;
?private String company;
?@Overridepublic Resume clone() {try {return (Resume) super.clone();} catch (CloneNotSupportedException e) {throw new AssertionError();}}
}
?
package designmode.prototype;
?
/*** 調用者*/
public class Invoker {public static void main(String[] args) {Resume resume = new Resume();resume.setName("張三");resume.setSex("男");resume.setAge("25");resume.setTimeArea("2018-2020");resume.setCompany("華為");
?Resume resume1 = resume.clone();//深復制resume1.setTimeArea("2020-至今");resume1.setCompany("中興");
?System.out.println(resume);System.out.println(resume1);}
}
?
結果:
Resume(name=張三, sex=男, age=25, timeArea=2018-2020, company=華為) Resume(name=張三, sex=男, age=25, timeArea=2020-至今, company=中興)
十二、狀態模式(state)
在狀態模式(State Pattern)中,類的行為是基于它的狀態改變的,這種類型的設計模式屬于行為型模式。
在狀態模式中,我們創建表示各種狀態的對象和一個行為隨著狀態對象改變而改變的 context 對象。
狀態模式允許對象在內部狀態改變時改變其行為,使得對象在不同的狀態下有不同的行為表現。通過將每個狀態封裝成獨立的類,可以避免使用大量的條件語句來實現狀態切換。
介紹
意圖
允許一個對象在其內部狀態改變時改變其行為,看起來就像是改變了其類一樣。
主要解決的問題
-
狀態模式解決對象行為依賴于其狀態的問題,使得對象可以在狀態變化時切換行為。
使用場景
-
當代碼中存在大量條件語句,且這些條件語句依賴于對象的狀態時。
實現方式
-
定義狀態接口:聲明一個或多個方法,用于封裝具體狀態的行為。
-
創建具體狀態類:實現狀態接口,根據狀態的不同實現具體的行為。
-
定義上下文類:包含一個狀態對象的引用,并在狀態改變時更新其行為。
關鍵代碼
-
狀態接口:聲明行為方法。
-
具體狀態類:實現狀態接口,封裝具體行為。
-
上下文類:維護一個狀態對象,并提供方法以改變其狀態。
應用實例
-
籃球運動員狀態:運動員可以有正常、不正常和超常等狀態。
-
曾侯乙編鐘:編鐘作為上下文,不同的鐘(狀態)有不同的演奏效果。
優點
-
封裝狀態轉換規則:將狀態轉換邏輯封裝在狀態對象內部。
-
易于擴展:增加新的狀態類不會影響現有代碼。
-
集中狀態相關行為:將所有與特定狀態相關的行為集中到一個類中。
-
簡化條件語句:避免使用大量的條件語句來切換行為。
-
狀態共享:允許多個上下文對象共享同一個狀態對象。
缺點
-
增加類和對象數量:每個狀態都需要一個具體的狀態類。
-
實現復雜:模式結構和實現相對復雜。
-
開閉原則支持不足:增加新狀態或修改狀態行為可能需要修改現有代碼。
使用建議
-
當對象的行為隨狀態改變而變化時,考慮使用狀態模式。
-
狀態模式適用于替代復雜的條件或分支語句。
注意事項
-
狀態模式適用于狀態數量有限(通常不超過5個)的情況。
-
謹慎使用,以避免系統變得過于復雜。
結構
狀態模式包含以下幾個主要角色:
-
上下文(Context):定義了客戶感興趣的接口,并維護一個當前狀態對象的引用。上下文可以通過狀態對象來委托處理狀態相關的行為。
-
狀態(State):定義了一個接口,用于封裝與上下文相關的一個狀態的行為。
-
具體狀態(Concrete State):實現了狀態接口,負責處理與該狀態相關的行為。具體狀態對象通常會在內部維護一個對上下文對象的引用,以便根據不同的條件切換到不同的狀態。
實現
我們將創建一個 State 接口和實現了 State 接口的實體狀態類。Context 是一個帶有某個狀態的類。
StatePatternDemo,我們的演示類使用 Context 和狀態對象來演示 Context 在狀態改變時的行為變化。
package designmode.state;import lombok.Data;/*** 上下文類*/
@Data
public class Context {private State state;
}package designmode.state;/*** 狀態類*/
public abstract class State {public abstract void doAction(Context context);
}package designmode.state;/*** 開始狀態類*/
public class StartState extends State{@Overridepublic void doAction(Context context) {context.setState(this);}public String toString(){return "開始狀態";}
}package designmode.state;/*** 停止狀態類*/
public class StopState extends State{@Overridepublic void doAction(Context context) {context.setState(this);}@Overridepublic String toString() {return "停止狀態";}
}
package designmode.state;/*** 調用者*/
public class Invoker {public static void main(String[] args) {Context context = new Context();StartState startState = new StartState();startState.doAction(context);System.out.println(context.getState().toString());StopState stopState = new StopState();stopState.doAction(context);System.out.println(context.getState().toString());}
}
結果: 開始狀態 停止狀態
十三、適配器模式(adapter)
適配器模式(Adapter Pattern)充當兩個不兼容接口之間的橋梁,屬于結構型設計模式。它通過一個中間件(適配器)將一個類的接口轉換成客戶期望的另一個接口,使原本不能一起工作的類能夠協同工作。
這種模式涉及到一個單一的類,該類負責加入獨立的或不兼容的接口功能。舉個真實的例子,讀卡器是作為內存卡和筆記本之間的適配器。您將內存卡插入讀卡器,再將讀卡器插入筆記本,這樣就可以通過筆記本來讀取內存卡。
假設有一個音頻播放器,它只能播放 MP3 文件。現在,我們需要播放 VLC 和 MP4 文件,可以通過創建一個適配器來實現:
-
目標接口:定義一個可以播放多種格式文件的音頻播放器接口。
-
適配者類:現有的音頻播放器,只能播放 MP3 文件。
-
適配器類:創建一個新的類,實現目標接口,并在內部使用適配者類來播放 MP3 文件,同時添加對 VLC 和 MP4 文件的支持。
概述
適配器模式是一種軟件設計模式,旨在解決不同接口之間的兼容性問題。
目的:將一個類的接口轉換為另一個接口,使得原本不兼容的類可以協同工作。
主要解決的問題:在軟件系統中,需要將現有的對象放入新環境,而新環境要求的接口與現有對象不匹配。
使用場景
-
需要使用現有類,但其接口不符合系統需求。
-
希望創建一個可復用的類,與多個不相關的類(包括未來可能引入的類)一起工作,這些類可能沒有統一的接口。
-
通過接口轉換,將一個類集成到另一個類系中。
實現方式
-
繼承或依賴:推薦使用依賴關系,而不是繼承,以保持靈活性。
關鍵代碼
適配器通過繼承或依賴現有對象,并實現所需的目標接口。
應用實例
-
電壓適配器:將 110V 電壓轉換為 220V,以適配不同國家的電器標準。
-
接口轉換:例如,將 Java JDK 1.1 的 Enumeration 接口轉換為 1.2 的 Iterator 接口。
-
跨平臺運行:在Linux上運行Windows程序。
-
數據庫連接:Java 中的 JDBC 通過適配器模式與不同類型的數據庫進行交互。
優點
-
促進了類之間的協同工作,即使它們沒有直接的關聯。
-
提高了類的復用性。
-
增加了類的透明度。
-
提供了良好的靈活性。
缺點
-
過度使用適配器可能導致系統結構混亂,難以理解和維護。
-
在Java中,由于只能繼承一個類,因此只能適配一個類,且目標類必須是抽象的。
使用建議
-
適配器模式應謹慎使用,特別是在詳細設計階段,它更多地用于解決現有系統的問題。
-
在考慮修改一個正常運行的系統接口時,適配器模式是一個合適的選擇。
通過這種方式,適配器模式可以清晰地表達其核心概念和應用,同時避免了不必要的復雜性。
結構
適配器模式包含以下幾個主要角色:
-
目標接口(Target):定義客戶需要的接口。
-
適配者類(Adaptee):定義一個已經存在的接口,這個接口需要適配。
-
適配器類(Adapter):實現目標接口,并通過組合或繼承的方式調用適配者類中的方法,從而實現目標接口。
實現
我們有一個 MediaPlayer 接口和一個實現了 MediaPlayer 接口的實體類 AudioPlayer。默認情況下,AudioPlayer 可以播放 mp3 格式的音頻文件。
我們還有另一個接口 AdvancedMediaPlayer 和實現了 AdvancedMediaPlayer 接口的實體類。該類可以播放 vlc 和 mp4 格式的文件。
我們想要讓 AudioPlayer 播放其他格式的音頻文件。為了實現這個功能,我們需要創建一個實現了 MediaPlayer 接口的適配器類 MediaAdapter,并使用 AdvancedMediaPlayer 對象來播放所需的格式。
AudioPlayer 使用適配器類 MediaAdapter 傳遞所需的音頻類型,不需要知道能播放所需格式音頻的實際類。AdapterPatternDemo 類使用 AudioPlayer 類來播放各種格式。
public interface MediaPlayer {public void play(String audioType, String fileName);
}
public interface AdvancedMediaPlayer { public void playVlc(String fileName);public void playMp4(String fileName);
}
public class VlcPlayer implements AdvancedMediaPlayer{@Overridepublic void playVlc(String fileName) {System.out.println("Playing vlc file. Name: "+ fileName); ? ? ?}@Overridepublic void playMp4(String fileName) {//什么也不做}
}
public class Mp4Player implements AdvancedMediaPlayer{@Overridepublic void playVlc(String fileName) {//什么也不做}@Overridepublic void playMp4(String fileName) {System.out.println("Playing mp4 file. Name: "+ fileName); ? ? ?}
}
public class MediaAdapter implements MediaPlayer {AdvancedMediaPlayer advancedMusicPlayer;public MediaAdapter(String audioType){if(audioType.equalsIgnoreCase("vlc") ){advancedMusicPlayer = new VlcPlayer(); ? ? ? } else if (audioType.equalsIgnoreCase("mp4")){advancedMusicPlayer = new Mp4Player();} ?}@Overridepublic void play(String audioType, String fileName) {if(audioType.equalsIgnoreCase("vlc")){advancedMusicPlayer.playVlc(fileName);}else if(audioType.equalsIgnoreCase("mp4")){advancedMusicPlayer.playMp4(fileName);}}
}
public class AudioPlayer implements MediaPlayer {MediaAdapter mediaAdapter; @Overridepublic void play(String audioType, String fileName) { ? ?//播放 mp3 音樂文件的內置支持if(audioType.equalsIgnoreCase("mp3")){System.out.println("Playing mp3 file. Name: "+ fileName); ? ? ? ? } //mediaAdapter 提供了播放其他文件格式的支持else if(audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")){mediaAdapter = new MediaAdapter(audioType);mediaAdapter.play(audioType, fileName);}else{System.out.println("Invalid media. "+audioType + " format not supported");}} ?
}
public class AdapterPatternDemo {public static void main(String[] args) {AudioPlayer audioPlayer = new AudioPlayer();audioPlayer.play("mp3", "beyond the horizon.mp3");audioPlayer.play("mp4", "alone.mp4");audioPlayer.play("vlc", "far far away.vlc");audioPlayer.play("avi", "mind me.avi");}
}
?
結果:
Playing mp3 file. Name: beyond the horizon.mp3
Playing mp4 file. Name:alone.mp4
Playing vlc fiel. Name:far far away.vlc
Invalid media. avi format not supported
十四、備忘錄模式(memento)
備忘錄模式(Memento Pattern)保存一個對象的某個狀態,以便在適當的時候恢復對象,備忘錄模式屬于行為型模式。
備忘錄模式允許在不破壞封裝性的前提下,捕獲和恢復對象的內部狀態。
介紹
意圖
在不破壞封裝性的前提下,捕獲一個對象的內部狀態,并允許在對象之外保存和恢復這些狀態。
主要解決的問題
-
允許捕獲并保存一個對象的內部狀態,以便在將來可以恢復到該狀態,實現撤銷和回滾操作。
使用場景
-
當需要提供一種撤銷機制,允許用戶回退到之前的狀態時。
實現方式
-
創建備忘錄類:用于存儲和封裝對象的狀態。
-
創建發起人角色:負責創建備忘錄,并根據需要恢復狀態。
-
創建備忘錄管理類(可選):負責管理所有備忘錄對象。
關鍵代碼
-
備忘錄:存儲發起人的狀態信息。
-
發起人:創建備忘錄,并根據備忘錄恢復狀態。
應用實例
-
后悔藥:提供一種撤銷操作的功能。
-
游戲存檔:保存游戲進度,允許玩家加載之前的存檔。
-
Windows中的Ctrl+Z:實現撤銷操作。
-
IE瀏覽器的后退:允許用戶回退到之前的頁面。
-
數據庫事務管理:通過事務日志保存狀態,實現回滾。
優點
-
提供狀態恢復機制:允許用戶方便地回到歷史狀態。
-
封裝狀態信息:用戶不需要關心狀態的保存細節。
缺點
-
資源消耗:如果對象的狀態復雜,保存狀態可能會占用較多資源。
使用建議
-
在需要保存和恢復數據狀態的場景中使用備忘錄模式。
-
考慮使用原型模式結合備忘錄模式,以節約內存。
注意事項
-
為了降低耦合度,應通過備忘錄管理類間接管理備忘錄對象。
-
備忘錄模式應謹慎使用,避免過度消耗系統資源。
結構
備忘錄模式包含以下幾個主要角色:
-
備忘錄(Memento):負責存儲原發器對象的內部狀態。備忘錄可以保持原發器的狀態的一部分或全部信息。
-
原發器(Originator):創建一個備忘錄對象,并且可以使用備忘錄對象恢復自身的內部狀態。原發器通常會在需要保存狀態的時候創建備忘錄對象,并在需要恢復狀態的時候使用備忘錄對象。
-
負責人(Caretaker):負責保存備忘錄對象,但是不對備忘錄對象進行操作或檢查。負責人只能將備忘錄傳遞給其他對象。
實現
備忘錄模式使用三個類 Memento、Originator 和 CareTaker。Memento 包含了要被恢復的對象的狀態。Originator 創建并在 Memento 對象中存儲狀態。Caretaker 對象負責從 Memento 中恢復對象的狀態。
MementoPatternDemo,我們的演示類使用 CareTaker 和 Originator 對象來顯示對象的狀態恢復。
package designmode.memento;
?
import lombok.Data;
?
/*** 備忘錄*/
@Data
public class Memento {
?private String state;
}package designmode.memento;
?
import lombok.Data;
?
/*** 發起人*/
@Data
public class Originator {
?private String state;
?public void setMemento(Memento memento){state = memento.getState();}
?public void show(){System.out.println("state:"+state);}
?public Memento createMemento() {Memento memento = new Memento();memento.setState(state);
?return memento;}
}
?
package designmode.memento;
?
import lombok.Data;
?
/*** 管理者類*/
@Data
public class Caretaker {
?private Memento memento;
}
?
package designmode.memento;
?
/*** 調用者*/
public class Invoker {public static void main(String[] args) {
?// 創建發起人,并設置初始狀態Originator originator = new Originator();originator.setState("On");originator.show();
?// 創建備忘錄,并保存當前狀態Caretaker caretaker = new Caretaker();caretaker.setMemento(originator.createMemento());
?// 改變發起人的狀態originator.setState("Off");originator.show();
?// 恢復發起人的狀態originator.setMemento(caretaker.getMemento());originator.show();}
}
?
結果: state:On state:Off state:On
十五、組合模式(component)
組合模式(Composite Pattern),又叫部分整體模式,是用于把一組相似的對象當作一個單一的對象。組合模式依據樹形結構來組合對象,用來表示部分以及整體層次。這種類型的設計模式屬于結構型模式,它創建了對象組的樹形結構。
這種模式創建了一個包含自己對象組的類。該類提供了修改相同對象組的方式。
我們通過下面的實例來演示組合模式的用法。實例演示了一個組織中員工的層次結構。
介紹
意圖
將對象組合成樹形結構以表示"部分-整體"的層次結構。組合模式使得用戶對單個對象和組合對象的使用具有一致性。
主要解決的問題
-
簡化樹形結構中對象的處理,無論它們是單個對象還是組合對象。
-
解耦客戶端代碼與復雜元素的內部結構,使得客戶端可以統一處理所有類型的節點。
使用場景
-
當需要表示對象的層次結構時,如文件系統或組織結構。
-
當希望客戶端代碼能夠以一致的方式處理樹形結構中的所有對象時。
實現方式
-
統一接口:定義一個接口,所有對象(樹枝和葉子)都實現這個接口。
-
組合結構:樹枝對象包含一個接口的引用列表,這些引用可以是葉子或樹枝。
關鍵代碼
-
Component接口:定義了所有對象必須實現的操作。
-
Leaf類:實現Component接口,代表樹中的葉子節點。
-
Composite類:也實現Component接口,并包含其他Component對象的集合。
應用實例
-
算術表達式:構建一個由操作數、操作符和子表達式組成的樹形結構。
-
GUI組件:在Java的AWT和Swing庫中,容器(如Panel)可以包含其他組件(如按鈕和復選框)。
優點
-
簡化客戶端代碼:客戶端可以統一處理所有類型的節點。
-
易于擴展:可以輕松添加新的葉子類型或樹枝類型。
缺點
-
違反依賴倒置原則:組件的聲明是基于具體類而不是接口,這可能導致代碼的靈活性降低。
使用建議
-
在設計時,優先使用接口而非具體類,以提高系統的靈活性和可維護性。
-
適用于需要處理復雜樹形結構的場景,如文件系統、組織結構等。
注意事項
-
在實現時,確保所有組件都遵循統一的接口,以保持一致性。
-
考慮使用工廠模式來創建不同類型的組件,以進一步解耦組件的創建邏輯。
結構
組合模式的核心角色包括:
-
組件(Component):
-
定義了組合中所有對象的通用接口,可以是抽象類或接口。它聲明了用于訪問和管理子組件的方法,包括添加、刪除、獲取子組件等。
-
-
葉子節點(Leaf):
-
表示組合中的葉子節點對象,葉子節點沒有子節點。它實現了組件接口的方法,但通常不包含子組件。
-
-
復合節點(Composite):
-
表示組合中的復合對象,復合節點可以包含子節點,可以是葉子節點,也可以是其他復合節點。它實現了組件接口的方法,包括管理子組件的方法。
-
-
客戶端(Client):
-
通過組件接口與組合結構進行交互,客戶端不需要區分葉子節點和復合節點,可以一致地對待整體和部分。
-
實現
我們有一個類 Employee,該類被當作組合模型類。CompositePatternDemo 類使用 Employee 類來添加部門層次結構,并打印所有員工。
package designmode.component;
?
import lombok.Data;
?
import java.util.ArrayList;
import java.util.List;
?
/*** 員工*/
@Data
public class Employee {
?private String name;
?private String dept;
?private int salary;
?private List<Employee> subEmployees;
?Employee(String name, String dept, int salary) {this.name = name;this.dept = dept;this.salary = salary;subEmployees = new ArrayList<>();}
?public void add(Employee employee){subEmployees.add(employee);}
?public void remove(Employee employee){subEmployees.remove(employee);}
?
}
?
package designmode.component;
?
/*** 調用類*/
public class Invoker {
?public static void main(String[] args) {// CEOEmployee ceo = new Employee("張三", "CEO", 50000);// 銷售部門經理Employee headSales = new Employee("李四", "銷售部門經理", 20000);// 超市部門經理Employee headMarketing = new Employee("王五", "超市部門經理", 20000);
?//專員Employee clerk1 = new Employee("Laura","專員", 10000);Employee clerk2 = new Employee("Bob","專員", 10000);
?//銷售Employee salesExecutive1 = new Employee("Richard","銷售", 10000);Employee salesExecutive2 = new Employee("Rob","銷售", 10000);
?ceo.add(headSales);ceo.add(headMarketing);headSales.add(salesExecutive1);headSales.add(salesExecutive2);
?headMarketing.add(clerk1);headMarketing.add(clerk2);
?System.out.println("=====打印所有該組織的員工=====");System.out.println(ceo);for (Employee subEmployee : ceo.getSubEmployees()) {System.out.println(subEmployee);for (Employee subEmployee1 : subEmployee.getSubEmployees()) {System.out.println(subEmployee1);}}}
}
?
結果:
=====打印所有該組織的員工===== Employee(name=張三, dept=CEO, salary=50000, subEmployees=[Employee(name=李四, dept=銷售部門經理, salary=20000, subEmployees=[Employee(name=Richard, dept=銷售, salary=10000, subEmployees=[]), Employee(name=Rob, dept=銷售, salary=10000, subEmployees=[])]), Employee(name=王五, dept=超市部門經理, salary=20000, subEmployees=[Employee(name=Laura, dept=專員, salary=10000, subEmployees=[]), Employee(name=Bob, dept=專員, salary=10000, subEmployees=[])])]) Employee(name=李四, dept=銷售部門經理, salary=20000, subEmployees=[Employee(name=Richard, dept=銷售, salary=10000, subEmployees=[]), Employee(name=Rob, dept=銷售, salary=10000, subEmployees=[])]) Employee(name=Richard, dept=銷售, salary=10000, subEmployees=[]) Employee(name=Rob, dept=銷售, salary=10000, subEmployees=[]) Employee(name=王五, dept=超市部門經理, salary=20000, subEmployees=[Employee(name=Laura, dept=專員, salary=10000, subEmployees=[]), Employee(name=Bob, dept=專員, salary=10000, subEmployees=[])]) Employee(name=Laura, dept=專員, salary=10000, subEmployees=[]) Employee(name=Bob, dept=專員, salary=10000, subEmployees=[])
十六、迭代器模式(Iterator )
迭代器模式(Iterator Pattern)是 Java 和 .Net 編程環境中非常常用的設計模式。
迭代器模式提供一種方法順序訪問一個聚合對象中的各個元素,而又不暴露其內部的表示。
迭代器模式屬于行為型模式。
介紹
意圖
允許順序訪問一個聚合對象中的元素,同時不暴露對象的內部表示。
主要解決的問題
-
提供一種統一的方法來遍歷不同的聚合對象。
使用場景
-
當需要遍歷一個聚合對象,而又不希望暴露其內部結構時。
實現方式
-
定義迭代器接口:包含
hasNext()
和next()
等方法,用于遍歷元素。 -
創建具體迭代器:實現迭代器接口,定義如何遍歷特定的聚合對象。
-
聚合類:定義一個接口用于返回一個迭代器對象。
關鍵代碼
-
迭代器接口:規定了遍歷元素的方法。
-
具體迭代器:實現了迭代器接口,包含遍歷邏輯。
應用實例
-
Java中的Iterator:Java集合框架中的迭代器用于遍歷集合元素。
優點
-
支持多種遍歷方式:不同的迭代器可以定義不同的遍歷方式。
-
簡化聚合類:聚合類不需要關心遍歷邏輯。
-
多遍歷支持:可以同時對同一個聚合對象進行多次遍歷。
-
擴展性:增加新的聚合類和迭代器類都很方便,無需修改現有代碼。
缺點
-
系統復雜性:每增加一個聚合類,就需要增加一個對應的迭代器類,增加了類的數量。
使用建議
-
當需要訪問聚合對象內容而不暴露其內部表示時,使用迭代器模式。
-
當需要為聚合對象提供多種遍歷方式時,考慮使用迭代器模式。
注意事項
-
迭代器模式通過分離集合對象的遍歷行為,使得外部代碼可以透明地訪問集合內部數據,同時不暴露集合的內部結構。
結構
迭代器模式包含以下幾個主要角色:
-
迭代器接口(Iterator):定義了訪問和遍歷聚合對象中各個元素的方法,通常包括獲取下一個元素、判斷是否還有元素、獲取當前位置等方法。
-
具體迭代器(Concrete Iterator):實現了迭代器接口,負責對聚合對象進行遍歷和訪問,同時記錄遍歷的當前位置。
-
聚合對象接口(Aggregate):定義了創建迭代器對象的接口,通常包括一個工廠方法用于創建迭代器對象。
-
具體聚合對象(Concrete Aggregate):實現了聚合對象接口,負責創建具體的迭代器對象,并提供需要遍歷的數據。
實現
我們將創建一個敘述導航方法的 Iterator 接口和一個返回迭代器的 Container 接口。實現了 Container 接口的實體類將負責實現 Iterator 接口。
IteratorPatternDemo,我們的演示類使用實體類 NamesRepository 來打印 NamesRepository 中存儲為集合的 Names。
package com.example.designmodestudy.iterator;
?
public interface Container {Iterator getIterator();
}
?
package com.example.designmodestudy.iterator;
?
public class NameRepository implements Container{
?public String[] names = {"張三" , "李四" ,"王五" , "趙六"};
?
?@Overridepublic Iterator getIterator() {return new NameIterator();}
?private class NameIterator implements Iterator {
?int index;
?@Overridepublic boolean hasNext() {if(index < names.length){return true;}return false;}
?@Overridepublic Object next() {if(this.hasNext()){return names[index++];}return null;}}
}
?
package com.example.designmodestudy.iterator;
?
import java.util.Objects;
?
public class Invoker {
?public static void main(String[] args) {NameRepository namesRepository = new NameRepository();Iterator iter = namesRepository.getIterator();
?do {String name = (String)iter.next();System.out.println("Name : " + name);} while (Objects.nonNull(iter) && iter.hasNext());
?}
}
?
結果: Name : 張三 Name : 李四 Name : 王五 Name : 趙六
十七、單例模式(Singleton )
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一。這種類型的設計模式屬于創建型模式,它提供了一種創建對象的最佳方式。
這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。
單例模式是一種創建型設計模式,它確保一個類只有一個實例,并提供了一個全局訪問點來訪問該實例。
注意:
-
1、單例類只能有一個實例。
-
2、單例類必須自己創建自己的唯一實例。
-
3、單例類必須給所有其他對象提供這一實例。
概要
單例模式(Singleton Pattern)
意圖
確保一個類只有一個實例,并提供一個全局訪問點來訪問該實例。
主要解決
頻繁創建和銷毀全局使用的類實例的問題。
何時使用
當需要控制實例數目,節省系統資源時。
如何解決
檢查系統是否已經存在該單例,如果存在則返回該實例;如果不存在則創建一個新實例。
關鍵代碼
構造函數是私有的。
應用實例
-
一個班級只有一個班主任。
-
Windows 在多進程多線程環境下操作文件時,避免多個進程或線程同時操作一個文件,需要通過唯一實例進行處理。
-
設備管理器設計為單例模式,例如電腦有兩臺打印機,避免同時打印同一個文件。
優點
-
內存中只有一個實例,減少內存開銷,尤其是頻繁創建和銷毀實例時(如管理學院首頁頁面緩存)。
-
避免資源的多重占用(如寫文件操作)。
缺點
-
沒有接口,不能繼承。
-
與單一職責原則沖突,一個類應該只關心內部邏輯,而不關心實例化方式。
使用場景
-
生成唯一序列號。
-
WEB 中的計數器,避免每次刷新都在數據庫中增加計數,先緩存起來。
-
創建消耗資源過多的對象,如 I/O 與數據庫連接等。
注意事項
-
線程安全:
getInstance()
方法中需要使用同步鎖synchronized (Singleton.class)
防止多線程同時進入造成實例被多次創建。 -
延遲初始化:實例在第一次調用
getInstance()
方法時創建。 -
序列化和反序列化:重寫
readResolve
方法以確保反序列化時不會創建新的實例。 -
反射攻擊:在構造函數中添加防護代碼,防止通過反射創建新實例。
-
類加載器問題:注意復雜類加載環境可能導致的多個實例問題。
-
靜態變量?:被static修飾的變量稱為靜態變量或類變量,它屬于類本身,不屬于任何對象實例。靜態變量在類加載時初始化,并且所有對象共享同一個靜態變量實例。例如,一個類中的計數器變量通常定義為靜態變量,因為它需要在多個對象之間共享?。
-
?靜態方法?:被static修飾的方法稱為靜態方法或類方法。靜態方法不需要創建對象即可調用,直接通過類名訪問。靜態方法不能訪問非靜態成員(即實例變量和實例方法),也不能使用this關鍵字?。
結構
單例模式包含以下幾個主要角色:
-
單例類:包含單例實例的類,通常將構造函數聲明為私有。
-
靜態成員變量:用于存儲單例實例的靜態成員變量。
-
獲取實例方法:靜態方法,用于獲取單例實例。
-
私有構造函數:防止外部直接實例化單例類。
-
線程安全處理:確保在多線程環境下單例實例的創建是安全的。
實現
我們將創建一個 SingleObject 類。SingleObject 類有它的私有構造函數和本身的一個靜態實例。
SingleObject 類提供了一個靜態方法,供外界獲取它的靜態實例。SingletonPatternDemo 類使用 SingleObject 類來獲取 SingleObject 對象。
單例模式的幾種實現方式
單例模式的實現有多種方式,如下所示:
1、懶漢式,線程不安全
是否 Lazy 初始化:是
是否多線程安全:否
實現難度:易
描述:這種方式是最基本的實現方式,這種實現最大的問題就是不支持多線程。因為沒有加鎖 synchronized,所以嚴格意義上它并不算單例模式。 這種方式 lazy loading 很明顯,不要求線程安全,在多線程不能正常工作。
public class Singleton { ?private static Singleton instance; ?private Singleton (){} ?public static Singleton getInstance() { ?if (instance == null) { ?instance = new Singleton(); ?//return new Singleton(); 錯誤應用} ?return instance; ?} ?
}
2、懶漢式,線程安全
是否 Lazy 初始化:是
是否多線程安全:是
實現難度:易
描述:這種方式具備很好的 lazy loading,能夠在多線程中很好的工作,但是,效率很低,99% 情況下不需要同步。 優點:第一次調用才初始化,避免內存浪費。 缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。 getInstance() 的性能對應用程序不是很關鍵(該方法使用不太頻繁)。
package com.example.designmodestudy.singleton.lazy;
?
import com.example.designmodestudy.singleton.User;
?
public class SingleObject {
?private static User instance;
?private SingleObject(){}
?public static synchronized User getInstance(){if(instance == null){System.out.println("懶漢式 loading...");instance = new User();//return new User(); 錯誤應用}return instance;}
}
2、餓漢式
是否 Lazy 初始化:否
是否多線程安全:是
實現難度:易
描述:這種方式比較常用,但容易產生垃圾對象。 優點:沒有加鎖,執行效率會提高。 缺點:類加載時就初始化,浪費內存。 它基于 classloader 機制避免了多線程的同步問題,不過,instance 在類裝載時就實例化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是調用 getInstance 方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化 instance 顯然沒有達到 lazy loading 的效果。
package com.example.designmodestudy.singleton.hungry;
?
import com.example.designmodestudy.singleton.User;
?
public class SingleObject {
?private static User instance = new User();
?private ?SingleObject(){}
?public static User getInstance(){System.out.println("餓漢式 loading...");return instance;}
}
?
package com.example.designmodestudy.singleton.hungry;
?
import com.example.designmodestudy.singleton.User;
?
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
?
public class Invoker {public static void main(String[] args) throws InterruptedException {
?ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(20));List<User> userList = new CopyOnWriteArrayList<>();
?for (int i = 0; i < 10; i++) {int finalI = i;threadPoolExecutor.execute(()->{
?User user = SingleObject.getInstance();user.setAge(25);user.setName("張三_" + finalI);userList.add(user);System.out.println(Thread.currentThread().getName() + ", 循環" + finalI);});}
?
?Thread.sleep(100);
?System.out.println("==============循環打印集合里的用戶數據=========================");for (User user : userList) {System.out.println(user);}
?threadPoolExecutor.shutdown();}
}
結果: 餓漢式 loading... 餓漢式 loading... 餓漢式 loading... 餓漢式 loading... pool-1-thread-1, 循環0 餓漢式 loading... 餓漢式 loading... pool-1-thread-2, 循環1 pool-1-thread-5, 循環4 pool-1-thread-4, 循環3 餓漢式 loading... 餓漢式 loading... pool-1-thread-1, 循環5 pool-1-thread-3, 循環2 餓漢式 loading... pool-1-thread-2, 循環6 pool-1-thread-5, 循環7 餓漢式 loading... pool-1-thread-1, 循環9 pool-1-thread-4, 循環8 ==============循環打印集合里的用戶數據========================= User(name=張三8, age=25) User(name=張三8, age=25) User(name=張三8, age=25) User(name=張三8, age=25) User(name=張三8, age=25) User(name=張三8, age=25) User(name=張三8, age=25) User(name=張三8, age=25) User(name=張三8, age=25) User(name=張三8, age=25)
4、雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)
JDK 版本:JDK1.5 起
是否 Lazy 初始化:是
是否多線程安全:是
實現難度:較復雜
描述:這種方式采用雙鎖機制,安全且在多線程情況下能保持高性能。 getInstance() 的性能對應用程序很關鍵。
package com.example.designmodestudy.singleton.dcl;
?
import com.example.designmodestudy.singleton.User;
?
public class SingleObject {
?private static volatile User instance;
?private SingleObject(){}
?public static User getInstance(){if(instance == null){System.out.println("dcl 懶漢式 first check loading...");synchronized (User.class){System.out.println("dcl 懶漢式 second check loading...");if(instance == null){instance = new User();}}
?}return instance;}
}
?
package com.example.designmodestudy.singleton.dcl;
?
import com.example.designmodestudy.singleton.User;
?
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
?
public class Invoker {public static void main(String[] args) throws InterruptedException {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(20));List<User> userList = new CopyOnWriteArrayList<>();
?for (int i = 0; i < 10; i++) {int finalI = i;threadPoolExecutor.execute(()->{
?User user = SingleObject.getInstance();user.setAge(25);user.setName("張三_" + finalI);userList.add(user);System.out.println(Thread.currentThread().getName() + ", 循環" + finalI);});}
?
?Thread.sleep(100);
?System.out.println("==============循環打印集合里的用戶數據=========================");for (User user : userList) {System.out.println(user);}
?threadPoolExecutor.shutdown();}
}
結果: dcl 懶漢式 first check loading... dcl 懶漢式 first check loading... dcl 懶漢式 first check loading... dcl 懶漢式 first check loading... dcl 懶漢式 first check loading... dcl 懶漢式 second check loading... dcl 懶漢式 second check loading... dcl 懶漢式 second check loading... dcl 懶漢式 second check loading... pool-1-thread-2, 循環1 pool-1-thread-4, 循環3 pool-1-thread-2, 循環5 pool-1-thread-4, 循環6 dcl 懶漢式 second check loading... pool-1-thread-5, 循環4 pool-1-thread-1, 循環0 pool-1-thread-5, 循環9 pool-1-thread-3, 循環2 pool-1-thread-4, 循環8 pool-1-thread-2, 循環7 ==============循環打印集合里的用戶數據========================= User(name=張三9, age=25) User(name=張三9, age=25) User(name=張三9, age=25) User(name=張三9, age=25) User(name=張三9, age=25) User(name=張三9, age=25) User(name=張三9, age=25) User(name=張三9, age=25) User(name=張三9, age=25) User(name=張三9, age=25)
十八、橋接模式(bridge)
橋接(Bridge)是用于把抽象化與實現化解耦,使得二者可以獨立變化。這種類型的設計模式屬于結構型模式,它通過提供抽象化和實現化之間的橋接結構,來實現二者的解耦。
這種模式涉及到一個作為橋接的接口,使得實體類的功能獨立于接口實現類,這兩種類型的類可被結構化改變而互不影響。
橋接模式的目的是將抽象與實現分離,使它們可以獨立地變化,該模式通過將一個對象的抽象部分與它的實現部分分離,使它們可以獨立地改變。它通過組合的方式,而不是繼承的方式,將抽象和實現的部分連接起來。
我們通過下面的實例來演示橋接模式(Bridge Pattern)的用法。其中,可以使用相同的抽象類方法但是不同的橋接實現類,來畫出不同顏色的圓。
介紹
意圖
用于將抽象部分與實現部分分離,使得它們可以獨立地變化。。
主要解決的問題
避免使用繼承導致的類爆炸問題,提供更靈活的擴展方式。
使用場景
當系統可能從多個角度進行分類,且每個角度都可能獨立變化時,橋接模式是合適的。
實現方式
-
分離多角度分類:將不同角度的分類邏輯分離,允許它們獨立變化。
-
減少耦合:降低抽象與實現之間的耦合度。
關鍵代碼
-
抽象類:定義一個抽象類,作為系統的一部分。
-
實現類:定義一個或多個實現類,與抽象類通過聚合(而非繼承)關聯。
應用實例
-
轉世投胎:靈魂(抽象)與肉體(實現)的分離,允許靈魂選擇不同的肉體。
-
墻上的開關:開關(抽象)與內部實現(實現)的分離,用戶無需關心開關的內部工作機制。
優點
-
抽象與實現分離:提高了系統的靈活性和可維護性。
-
擴展能力強:可以獨立地擴展抽象和實現。
-
實現細節透明:用戶不需要了解實現細節。
缺點
-
理解與設計難度:橋接模式增加了系統的理解與設計難度。
-
聚合關聯:要求開發者在抽象層進行設計與編程。
使用建議
-
當系統需要在抽象化角色和具體化角色之間增加靈活性時,考慮使用橋接模式。
-
對于不希望使用繼承或因多層次繼承導致類數量急劇增加的系統,橋接模式特別適用。
-
當一個類存在兩個獨立變化的維度,且這兩個維度都需要擴展時,使用橋接模式。
注意事項
-
橋接模式適用于兩個獨立變化的維度,確保它們可以獨立地擴展和變化。
結構
以下是橋接模式的幾個關鍵角色:
-
抽象(Abstraction):定義抽象接口,通常包含對實現接口的引用。
-
擴展抽象(Refined Abstraction):對抽象的擴展,可以是抽象類的子類或具體實現類。
-
實現(Implementor):定義實現接口,提供基本操作的接口。
-
具體實現(Concrete Implementor):實現實現接口的具體類。
實現
package com.example.designmodestudy.bridge;
?
public interface DrawAPI {public void drawCircle(int radius, int x, int y);
?
}
?
?
package com.example.designmodestudy.bridge;
?
public class GreenCircle implements DrawAPI{@Overridepublic void drawCircle(int radius, int x, int y) {System.out.println("Drawing Circle[ color: green, radius: "+ radius +", x: " +x+", "+ y +"]");}
}
?
package com.example.designmodestudy.bridge;
?
public class RedCircle implements DrawAPI{@Overridepublic void drawCircle(int radius, int x, int y) {System.out.println("Drawing Circle[ color: red, radius: "+ radius +", x: " +x+", "+ y +"]");}
}
?
package com.example.designmodestudy.bridge;
?
public abstract class Shape {
?protected DrawAPI drawAPI;
?public Shape(DrawAPI drawAPI){this.drawAPI = drawAPI;}
?abstract void draw();
}
?
?
package com.example.designmodestudy.bridge;
?
public class Circle extends Shape{
?private int x, y, radius;
?public Circle(int x, int y, int radius, DrawAPI drawAPI) {super(drawAPI);this.x = x;this.y = y;this.radius = radius;}
?@Overridevoid draw() {drawAPI.drawCircle(radius, x, y);}
}
?
?
package com.example.designmodestudy.bridge;
?
public class Invoker {public static void main(String[] args) {Shape redCircle = new Circle(50, 50 , 10, new RedCircle());Shape greenCircle = new Circle(50, 50, 10, new GreenCircle());
?redCircle.draw();greenCircle.draw();}
}
?
結果: Drawing Circle[ color: red, radius: 10, x: 50, 50] Drawing Circle[ color: green, radius: 10, x: 50, 50]
十九、責任鏈模式(Chain of Responsibility)
責任鏈模式(Chain of Responsibility Pattern)為請求創建了一個接收者對象的鏈。這種模式給予請求的類型,對請求的發送者和接收者進行解耦。這種類型的設計模式屬于行為型模式。
責任鏈模式通過將多個處理器(處理對象)以鏈式結構連接起來,使得請求沿著這條鏈傳遞,直到有一個處理器處理該請求為止。
責任鏈模式允許多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關系。將這些對象連成一條鏈,并沿著這條鏈傳遞請求。
介紹
意圖
允許將請求沿著處理者鏈傳遞,直到請求被處理為止。
主要解決的問題
-
解耦請求發送者和接收者,使多個對象都有可能接收請求,而發送者不需要知道哪個對象會處理它。
使用場景
-
當有多個對象可以處理請求,且具體由哪個對象處理由運行時決定時。
-
當需要向多個對象中的一個提交請求,而不想明確指定接收者時。
實現方式
-
定義處理者接口:所有處理者必須實現同一個接口。
-
創建具體處理者:實現接口的具體類,包含請求處理邏輯和指向鏈中下一個處理者的引用。
關鍵代碼
-
Handler接口:定義一個方法用于處理請求。
-
ConcreteHandler類:實現Handler接口,包含請求處理邏輯和對下一個處理者的引用。
應用實例
-
擊鼓傳花:游戲中的傳遞行為,直到音樂停止。
-
事件冒泡:在JavaScript中,事件從最具體的元素開始,逐級向上傳播。
-
Web服務器:如Apache Tomcat處理字符編碼,Struts2的攔截器,以及Servlet的Filter。
優點
-
降低耦合度:發送者和接收者之間解耦。
-
簡化對象:對象不需要知道鏈的結構。
-
靈活性:通過改變鏈的成員或順序,動態地新增或刪除責任。
-
易于擴展:增加新的請求處理類很方便。
缺點
-
請求未被處理:不能保證請求一定會被鏈中的某個處理者接收。
-
性能影響:可能影響系統性能,且調試困難,可能導致循環調用。
-
難以觀察:運行時特征不明顯,可能妨礙除錯。
使用建議
-
在處理請求時,如果有多個潛在的處理者,考慮使用責任鏈模式。
-
確保鏈中的每個處理者都明確知道如何傳遞請求到鏈的下一個環節。
注意事項
-
在Java Web開發中,責任鏈模式有廣泛應用,如過濾器鏈、攔截器等。
結構
主要涉及到以下幾個核心角色:
-
抽象處理者(Handler):
-
定義一個處理請求的接口,通常包含一個處理請求的方法(如
handleRequest
)和一個指向下一個處理者的引用(后繼者)。
-
-
具體處理者(ConcreteHandler):
-
實現了抽象處理者接口,負責處理請求。如果能夠處理該請求,則直接處理;否則,將請求傳遞給下一個處理者。
-
-
客戶端(Client):
-
創建處理者對象,并將它們連接成一條責任鏈。通常,客戶端只需要將請求發送給責任鏈的第一個處理者,無需關心請求的具體處理過程。
-
實現
我們創建抽象類 AbstractLogger,帶有詳細的日志記錄級別。然后我們創建三種類型的記錄器,都擴展了 AbstractLogger。每個記錄器消息的級別是否屬于自己的級別,如果是則相應地打印出來,否則將不打印并把消息傳給下一個記錄器。
package com.example.designmodestudy.chain;
?
public abstract class AbstractLogger {public static int INFO = 1;public static int DEBUG = 2;public static int ERROR = 3;
?protected int level;
?//責任鏈中的下一個元素protected AbstractLogger nextLogger;
?public void setNextLogger(AbstractLogger nextLogger){this.nextLogger = nextLogger;}
?public void logMessage(int level ,String message){if(this.level <= level){write(message);}if(nextLogger != null){nextLogger.logMessage(level, message);}}
?abstract void write(String message);
}
?
package com.example.designmodestudy.chain;
?
public class ConsoleLogger extends AbstractLogger{
?public ConsoleLogger(int level){this.level = level;}@Overridevoid write(String message) {System.out.println("Standard console::Logger:" + message);}
}
?
package com.example.designmodestudy.chain;
?
public class ErrorLogger extends AbstractLogger{public ErrorLogger(int level){this.level = level;}@Overridevoid write(String message) {System.out.println("Error Console::Logger:" + message);}
}
?
package com.example.designmodestudy.chain;
?
public class FileLogger extends AbstractLogger{public FileLogger(int level){this.level = level;}@Overridevoid write(String message) {System.out.println("Normal ? file::Logger: " + message);}
}
?
package com.example.designmodestudy.chain;
?
public class Invoker {
?private static AbstractLogger getChainOfLoggers(){AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
?errorLogger.setNextLogger(fileLogger);fileLogger.setNextLogger(consoleLogger);
?return errorLogger;}
?public static void main(String[] args) {AbstractLogger loggerChain = getChainOfLoggers();loggerChain.logMessage(AbstractLogger.INFO, AbstractLogger.INFO + "這是一條正常信息");loggerChain.logMessage(AbstractLogger.DEBUG, AbstractLogger.DEBUG + "這是一個debug信息");loggerChain.logMessage(AbstractLogger.ERROR, AbstractLogger.ERROR + "這是一個error信息");}
}
?
結果: Standard console::Logger:1這是一條正常消息 Normal file::Logger: 2這是一個debug消息 Standard console::Logger:2這是一個debug消息 Error Console::Logger:3這是一個error消息 Normal file::Logger: 3這是一個error消息 Standard console::Logger:3這是一個error消息
二十、中介者模式
中介者模式(Mediator Pattern)是用來降低多個對象和類之間的通信復雜性,屬于行為型模式。
中介者模式定義了一個中介對象來封裝一系列對象之間的交互。中介者使各對象之間不需要顯式地相互引用,從而使其耦合松散,且可以獨立地改變它們之間的交互。
介紹
意圖
通過引入一個中介者對象來封裝和協調多個對象之間的交互,從而降低對象間的耦合度。
主要解決的問題
-
解決對象間復雜的一對多關聯問題,避免對象之間的高度耦合,簡化系統結構。
使用場景
-
當系統中多個類相互耦合,形成網狀結構時。
實現方式
-
定義中介者接口:規定中介者必須實現的接口。
-
創建具體中介者:實現中介者接口,包含協調各同事對象交互的邏輯。
-
定義同事類:各個對象不需要顯式地相互引用,而是通過中介者來進行交互。
關鍵代碼
-
中介者:封裝了對象間的交互邏輯。
-
同事類:通過中介者進行通信。
應用實例
-
WTO:中國加入WTO后,各國通過WTO進行貿易,簡化了雙邊關系。
-
機場調度系統:協調飛機起降、跑道使用等。
-
MVC框架:控制器作為模型和視圖的中介者。
優點
-
降低復雜度:將多個對象間的一對多關系轉換為一對一關系。
-
解耦:對象之間不再直接引用,通過中介者進行交互。
-
符合迪米特原則:對象只需知道中介者,不需要知道其他對象。
缺點
-
中介者復雜性:中介者可能會變得龐大和復雜,難以維護。
使用建議
-
當系統中對象間存在復雜的引用關系時,考慮使用中介者模式。
-
通過中介者封裝多個類的行為,避免生成過多的子類。
注意事項
-
避免在職責不明確或混亂的情況下使用中介者模式,這可能導致中介者承擔過多職責。
結構
中介者模式包含以下幾個主要角色:
-
中介者(Mediator):定義了一個接口用于與各個同事對象通信,并管理各個同事對象之間的關系。通常包括一個或多個事件處理方法,用于處理各種交互事件。
-
具體中介者(Concrete Mediator):實現了中介者接口,負責實現各個同事對象之間的通信邏輯。它會維護一個對各個同事對象的引用,并協調它們的交互。
-
同事對象(Colleague):定義了一個接口,用于與中介者進行通信。通常包括一個發送消息的方法,以及一個接收消息的方法。
-
具體同事對象(Concrete Colleague):實現了同事對象接口,是真正參與到交互中的對象。它會將自己的消息發送給中介者,由中介者轉發給其他同事對象。
實現
package com.example.designmodestudy.mediator;
?
/*** 同事類*/
public abstract class Colleague {
?//中介者protected Mediator mediator;
?//同事名稱protected String name;
?public Colleague(Mediator mediator){this.mediator = mediator;mediator.addColleague(this);}
?//發送消息public abstract void sendMsg(String message);
?//接收消息public abstract void receiveMsg(String msg, Colleague sender);
?
}
package com.example.designmodestudy.mediator;
?
/*** 同事A*/
public class ConcreteColleagueA extends Colleague{
?public ConcreteColleagueA(Mediator mediator, String name) {super(mediator);this.name = name;}
?@Overridepublic void sendMsg(String message) {System.out.println(name + "發送消息:" + message);mediator.sendMsg(message, this);}
?@Overridepublic void receiveMsg(String msg, Colleague sender) {System.out.println("同事A收到來自 " + sender.name + " 消息:" + msg);}
}
?
?
package com.example.designmodestudy.mediator;
?
/*** 同事B*/
public class ConcreteColleagueB extends Colleague{
?public ConcreteColleagueB(Mediator mediator, String name) {super(mediator);this.name = name;}
?@Overridepublic void sendMsg(String message) {System.out.println(name + "發送消息:" + message);mediator.sendMsg(message, this);}
?@Overridepublic void receiveMsg(String msg, Colleague sender) {System.out.println("同事B收到來自 " + sender.name + " 消息:" + msg);}
}
package com.example.designmodestudy.mediator;
?
import java.util.ArrayList;
import java.util.List;
?
/*** 中介者抽象類*/
public abstract class Mediator {
?protected List<Colleague> colleagueList = new ArrayList<>();
?//添加同事public void addColleague(Colleague colleague){colleagueList.add(colleague);}
?//移除同事public void removeColleague(Colleague colleague){colleagueList.remove(colleague);}
?//發送消息abstract void sendMsg(String message, Colleague colleague);
}
?
?
package com.example.designmodestudy.mediator;
?
/*** 中介者實現類*/
public class ConcreteMediator extends Mediator{@Overridevoid sendMsg(String message, Colleague sender) {for (Colleague colleague : colleagueList) {if(colleague != sender){colleague.receiveMsg(message, sender);}}}
}
?
package com.example.designmodestudy.mediator;
?
public class Invoker {public static void main(String[] args) {Mediator mediator = new ConcreteMediator();Colleague colleagueA = new ConcreteColleagueA(mediator, "同事A");Colleague colleagueB = new ConcreteColleagueB(mediator, "同事B");
?colleagueA.sendMsg("你好!同事B!");colleagueB.sendMsg("你好!同事A!");}
}
?
結果: 同事A發送消息:你好!同事B! 同事B收到來自 同事A 消息:你好!同事B! 同事B發送消息:你好!同事A! 同事A收到來自 同事B 消息:你好!同事A!
二十一、享元模式
享元模式(Flyweight Pattern)主要用于減少創建對象的數量,以減少內存占用和提高性能。這種類型的設計模式屬于結構型模式,它提供了減少對象數量從而改善應用所需的對象結構的方式。
享元模式嘗試重用現有的同類對象,如果未找到匹配的對象,則創建新對象。我們將通過創建 5 個對象來畫出 20 個分布于不同位置的圓來演示這種模式。由于只有 5 種可用的顏色,所以 color 屬性被用來檢查現有的 Circle 對象。
概要
意圖
通過共享對象來減少創建大量相似對象時的內存消耗。。
主要解決的問題
-
避免因創建大量對象而導致的內存溢出問題。
-
通過共享對象,提高內存使用效率。
使用場景
-
當系統中存在大量相似或相同的對象。
-
對象的創建和銷毀成本較高。
-
對象的狀態可以外部化,即對象的部分狀態可以獨立于對象本身存在。
實現方式
-
定義享元接口:創建一個享元接口,規定可以共享的狀態。
-
創建具體享元類:實現該接口的具體類,包含內部狀態。
-
使用享元工廠:創建一個工廠類,用于管理享元對象的創建和復用。
關鍵代碼
-
HashMap:使用哈希表存儲已經創建的享元對象,以便快速檢索。
應用實例
-
Java中的String對象:字符串常量池中已經存在的字符串會被復用。
-
數據庫連接池:數據庫連接被復用,避免頻繁創建和銷毀連接。
優點
-
減少內存消耗:通過共享對象,減少了內存中對象的數量。
-
提高效率:減少了對象創建的時間,提高了系統效率。
缺點
-
增加系統復雜度:需要分離內部狀態和外部狀態,增加了設計和實現的復雜性。
-
線程安全問題:如果外部狀態處理不當,可能會引起線程安全問題。
使用建議
-
在創建大量相似對象時考慮使用享元模式。
-
確保享元對象的內部狀態是共享的,而外部狀態是獨立于對象的。
注意事項
-
狀態分離:明確區分內部狀態和外部狀態,避免混淆。
-
享元工廠:使用享元工廠來控制對象的創建和復用,確保對象的一致性和完整性。
結構
享元模式包含以下幾個核心角色:
-
享元工廠(Flyweight Factory):
-
負責創建和管理享元對象,通常包含一個池(緩存)用于存儲和復用已經創建的享元對象。
-
-
具體享元(Concrete Flyweight):
-
實現了抽象享元接口,包含了內部狀態和外部狀態。內部狀態是可以被共享的,而外部狀態則由客戶端傳遞。
-
-
抽象享元(Flyweight):
-
定義了具體享元和非共享享元的接口,通常包含了設置外部狀態的方法。
-
-
客戶端(Client):
-
使用享元工廠獲取享元對象,并通過設置外部狀態來操作享元對象。客戶端通常不需要關心享元對象的具體實現。
-
實現
我們將創建一個 Shape 接口和實現了 Shape 接口的實體類 Circle。下一步是定義工廠類 ShapeFactory。
ShapeFactory 有一個 Circle 的 HashMap,其中鍵名為 Circle 對象的顏色。無論何時接收到請求,都會創建一個特定顏色的圓。ShapeFactory 檢查它的 HashMap 中的 circle 對象,如果找到 Circle 對象,則返回該對象,否則將創建一個存儲在 hashmap 中以備后續使用的新對象,并把該對象返回到客戶端。
FlyWeightPatternDemo 類使用 ShapeFactory 來獲取 Shape 對象。它將向 ShapeFactory 傳遞信息(red / green / blue/ black / white),以便獲取它所需對象的顏色。
package com.example.designmodestudy.flyweight;
?
public interface Shape {
?void draw();
}
?
package com.example.designmodestudy.flyweight;
?
import lombok.Data;
?
@Data
public class Circle implements Shape{
?private String color;private int x;private int y;private int radius;
?public Circle(String color){this.color = color;}
?@Overridepublic void draw() {System.out.println(this.toString());}
}
?
package com.example.designmodestudy.flyweight;
?
import java.util.HashMap;
?
public class ShapeFactory {
?private static final HashMap<String ,Circle> circleMap = new HashMap<>();
?public static Circle getCircle(String color){Circle circle = circleMap.get(color);if(circle == null){circle = new Circle(color);circleMap.put(color, circle);System.out.println("Creating circle of color:" + color);}
?return circle;}
}
?
package com.example.designmodestudy.flyweight;
?
public class Invoke {
?private static final String colors[]={"Red", "Green", "Blue", "White", "Black" };
?public static void main(String[] args) {for (int i = 0; i < 10; i++) {Circle circle = ShapeFactory.getCircle(getRandomColor());circle.setX(getRandomX());circle.setY(getRandomY());circle.setRadius(100);circle.draw();}}
?private static String getRandomColor() {return colors[(int) (Math.random() * colors.length)];}
?private static int getRandomX(){return (int) (Math.random()*10);}
?private static int getRandomY(){return (int) (Math.random() * 10);}
}
?
結果: Creating circle of color:Black Circle(color=Black, x=5, y=0, radius=100) Creating circle of color:Red Circle(color=Red, x=2, y=3, radius=100) Creating circle of color:Blue Circle(color=Blue, x=1, y=3, radius=100) Circle(color=Blue, x=6, y=0, radius=100) Circle(color=Black, x=4, y=9, radius=100) Circle(color=Red, x=7, y=8, radius=100) Circle(color=Red, x=5, y=7, radius=100) Creating circle of color:Green Circle(color=Green, x=9, y=0, radius=100) Circle(color=Red, x=8, y=0, radius=100) Circle(color=Black, x=1, y=4, radius=100)
二十二、解釋器模式
解釋器模式(Interpreter Pattern)提供了評估語言的語法或表達式的方式,它屬于行為型模式。
解釋器模式給定一個語言,定義它的文法的一種表示,并定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。
這種模式被用在 SQL 解析、符號處理引擎等。
介紹
意圖
定義一種語言的文法表示,并創建一個解釋器,該解釋器能夠解釋該語言中的句子。
主要解決的問題
-
解釋器模式用于構建一個能夠解釋特定語言或文法的句子的解釋器。
使用場景
-
當某一特定類型的問題頻繁出現,并且可以通過一種簡單的語言來表達這些問題的實例時。
實現方式
-
定義文法:明確語言的終結符和非終結符。
-
構建語法樹:根據語言的句子構建對應的語法樹結構。
-
創建環境類:包含解釋過程中所需的全局信息,通常是一個HashMap。
關鍵代碼
-
終結符與非終結符:定義語言的文法結構。
-
環境類:存儲解釋過程中需要的外部環境信息。
應用實例
-
編譯器:解釋器模式可以用于編譯器設計,將源代碼解釋為目標代碼。
-
正則表達式:解釋器模式可以用于解析和執行正則表達式。
-
SQL解析:解釋器模式可以用于解析和執行SQL語句。
優點
-
可擴展性好:容易添加新的解釋表達式的方式。
-
靈活性:可以根據需要輕松擴展或修改文法。
-
易于實現簡單文法:對于簡單的語言,實現起來相對容易。
缺點
-
使用場景有限:只適用于適合使用解釋的簡單文法。
-
維護困難:對于復雜的文法,維護和擴展變得困難。
-
類膨脹:可能會產生很多類,每個文法規則對應一個類。
-
遞歸調用:解釋器模式通常使用遞歸調用,這可能難以理解和跟蹤。
使用建議
-
在需要解釋執行語言中的句子時,考慮使用解釋器模式。
-
確保文法簡單,以避免系統變得過于復雜。
注意事項
-
解釋器模式在 Java 中可能不是首選,如果遇到適用場景,可以考慮使用如expression4J之類的庫來代替。
結構
解釋器模式包含以下幾個主要角色:
-
抽象表達式(Abstract Expression):定義了解釋器的抽象接口,聲明了解釋操作的方法,通常是一個抽象類或接口。
-
終結符表達式(Terminal Expression):實現了抽象表達式接口的終結符表達式類,用于表示語言中的終結符(如變量、常量等),并實現了對應的解釋操作。
-
非終結符表達式(Non-terminal Expression):實現了抽象表達式接口的非終結符表達式類,用于表示語言中的非終結符(如句子、表達式等),并實現了對應的解釋操作。
-
上下文(Context):包含解釋器之外的一些全局信息,在解釋過程中提供給解釋器使用,通常用于存儲變量的值、保存解釋器的狀態等。
-
客戶端(Client):創建并配置具體的解釋器對象,并將需要解釋的表達式傳遞給解釋器進行解釋。
實現
我們將創建一個接口 Expression 和實現了 Expression 接口的實體類。定義作為上下文中主要解釋器的 TerminalExpression 類。其他的類 OrExpression、AndExpression 用于創建組合式表達式。
InterpreterPatternDemo,我們的演示類使用 Expression 類創建規則和演示表達式的解析。
package com.example.designmodestudy.interpreter;
?
public interface Expression {public boolean interpret(String context);
?
}
?
package com.example.designmodestudy.interpreter;
?
public class TerminalExpression implements Expression{
?private String data;
?public TerminalExpression(String data){this.data = data;}
?@Overridepublic boolean interpret(String context) {if(context.contains(data)){return true;}return false;}
}
?
package com.example.designmodestudy.interpreter;
?
public class OrExpression implements Expression{
?private Expression expression1 = null;private Expression expression2 = null;
?public OrExpression(Expression expression1, Expression expression2){this.expression1 = expression1;this.expression2 = expression2;}
?@Overridepublic boolean interpret(String context) {return expression1.interpret(context) || expression2.interpret(context);}
}
?
package com.example.designmodestudy.interpreter;
?
public class AndExpression implements Expression{
?private Expression expression1 = null;private Expression expression2 = null;
?public AndExpression(Expression expression1, Expression expression2){this.expression1 = expression1;this.expression2 = expression2;}
?@Overridepublic boolean interpret(String context) {return expression1.interpret(context) && expression2.interpret(context);}
}
?
package com.example.designmodestudy.interpreter;
?
public class Invoker {
?//規則: robert和john是男性public static Expression getMaleExpression(){Expression robert = new TerminalExpression("robert");Expression john = new TerminalExpression("John");return new OrExpression(robert, john);}
?//規則:julie是一個已婚的女性public static Expression getMarriedWomanExpression(){Expression julie = new TerminalExpression("Julie");Expression married = new TerminalExpression("Married");return new AndExpression(julie, married);}
?public static void main(String[] args) {Expression maleExpression = getMaleExpression();Expression marriedWomanExpression = getMarriedWomanExpression();
?String name = "John";System.out.println(name + "是男性嗎?" + maleExpression.interpret(name));name = "Julie";System.out.println(name + "是一個結婚的女性嗎?" + marriedWomanExpression.interpret("Married " + name));}
}
?
結果: John是男性嗎?true Julie是一個結婚的女性嗎?true
二十三、訪問者模式
在訪問者模式(Visitor Pattern)中,我們使用了一個訪問者類,它改變了元素類的執行算法。通過這種方式,元素的執行算法可以隨著訪問者改變而改變。這種類型的設計模式屬于行為型模式。根據模式,元素對象已接受訪問者對象,這樣訪問者對象就可以處理元素對象上的操作。
介紹
意圖
旨在將數據結構與在該數據結構上執行的操作分離,從而使得添加新的操作變得更容易,而無需修改數據結構本身。
主要解決的問題
-
解決在穩定數據結構和易變操作之間的耦合問題,使得操作可以獨立于數據結構變化。
使用場景
-
當需要對一個對象結構中的對象執行多種不同的且不相關的操作時,尤其是這些操作需要避免"污染"對象類本身。
實現方式
-
定義訪問者接口:聲明一系列訪問方法,一個訪問方法對應數據結構中的一個元素類。
-
創建具體訪問者:實現訪問者接口,為每個訪問方法提供具體實現。
-
定義元素接口:聲明一個接受訪問者的方法。
-
創建具體元素:實現元素接口,每個具體元素類對應數據結構中的一個具體對象。
關鍵代碼
-
訪問者接口:包含訪問不同元素的方法。
-
具體訪問者:實現了訪問者接口,包含對每個元素類的訪問邏輯。
-
元素接口:包含一個接受訪問者的方法。
-
具體元素:實現了元素接口,提供給訪問者訪問的入口。
應用實例
-
做客場景:訪問者(如您)訪問朋友家,朋友作為元素提供信息,訪問者根據信息做出判斷。
優點
-
單一職責原則:訪問者模式符合單一職責原則,每個類只負責一項職責。
-
擴展性:容易為數據結構添加新的操作。
-
靈活性:訪問者可以獨立于數據結構變化。
缺點
-
違反迪米特原則:元素需要向訪問者公開其內部信息。
-
元素類難以變更:元素類需要維持與訪問者的兼容。
-
依賴具體類:訪問者模式依賴于具體類而不是接口,違反了依賴倒置原則。
使用建議
-
當對象結構穩定,但需要在其上定義多種新操作時,考慮使用訪問者模式。
-
當需要避免操作"污染"對象類時,使用訪問者模式封裝操作。
注意事項
-
訪問者模式可以用于功能統一,如報表生成、用戶界面顯示、攔截器和過濾器等。
包含的幾個主要角色
-
訪問者(Visitor):
-
定義了訪問元素的接口。
-
-
具體訪問者(Concrete Visitor):
-
實現訪問者接口,提供對每個具體元素類的訪問和相應操作。
-
-
元素(Element):
-
定義了一個接受訪問者的方法。
-
-
具體元素(Concrete Element):
-
實現元素接口,提供一個
accept
方法,允許訪問者訪問并操作。
-
-
對象結構(Object Structure)(可選):
-
定義了如何組裝具體元素,如一個組合類。
-
-
客戶端(Client)(可選):
-
使用訪問者模式對對象結構進行操作。
-
實現
我們將創建一個定義接受操作的 ComputerPart 接口。Keyboard、Mouse、Monitor 和 Computer 是實現了 ComputerPart 接口的實體類。我們將定義另一個接口 ComputerPartVisitor,它定義了訪問者類的操作。Computer 使用實體訪問者來執行相應的動作。
VisitorPatternDemo,我們的演示類使用 Computer、ComputerPartVisitor 類來演示訪問者模式的用法。
package com.example.designmodestudy.visitor;
?
public interface ComputerPart {public void accept(ComputerPartVisitor computerPartVisitor);
}
?
package com.example.designmodestudy.visitor;
?
public class Keyboard implements ComputerPart{@Overridepublic void accept(ComputerPartVisitor computerPartVisitor) {computerPartVisitor.visit(this);}
}
?
package com.example.designmodestudy.visitor;
?
public class Monitor implements ComputerPart{@Overridepublic void accept(ComputerPartVisitor computerPartVisitor) {computerPartVisitor.visit(this);}
}
?
package com.example.designmodestudy.visitor;
?
public class Mouse implements ComputerPart{@Overridepublic void accept(ComputerPartVisitor computerPartVisitor) {computerPartVisitor.visit(this);}
}
?
package com.example.designmodestudy.visitor;
?
public class Computer implements ComputerPart{
?ComputerPart[] parts;
?public Computer(){parts = new ComputerPart[]{new Mouse(), new Keyboard(), new Monitor()};}
?@Overridepublic void accept(ComputerPartVisitor computerPartVisitor) {for (int i = 0; i < parts.length; i++) {parts[i].accept(computerPartVisitor);}computerPartVisitor.visit(this);}
}
?
package com.example.designmodestudy.visitor;
?
public interface ComputerPartVisitor {public void visit(Computer computer);public void visit(Mouse mouse);public void visit(Keyboard keyboard);public void visit(Monitor monitor);
}
?
package com.example.designmodestudy.visitor;
?
public class ComputerPartDisplayVisitor implements ComputerPartVisitor{@Overridepublic void visit(Computer computer) {System.out.println("顯示電腦");}
?@Overridepublic void visit(Mouse mouse) {System.out.println("顯示鼠標");}
?@Overridepublic void visit(Keyboard keyboard) {System.out.println("顯示鍵盤");}
?@Overridepublic void visit(Monitor monitor) {System.out.println("顯示監控");}
}
?
package com.example.designmodestudy.visitor;
?
public class Invoker {public static void main(String[] args) {ComputerPart computerPart = new Computer();computerPart.accept(new ComputerPartDisplayVisitor());}
}
?
結果: 顯示鼠標 顯示鍵盤 顯示監控 顯示電腦
參考文獻
1、設計模式 | 菜鳥教程
2、《大話設計模式》書籍