寫在前面
Hello,我是易元,這篇文章是我學習設計模式時的筆記和心得體會。如果其中有錯誤,歡迎大家留言指正!
文章為設計模式間的組合使用,涉及代碼較多,個人覺得熟能生巧,希望自己能從中學習到新的思維、新的靈感,希望大家也能有所收獲。
模式回顧
定義
橋接模式是一種結構型涉及模式,它旨在將抽象部分與其實現部分分離,使得它們可以獨立的變化。 橋接模式通過組合而非繼承的方式,將抽象和實現解耦,從而解決了傳統繼承在多維度變化時導致的類爆炸問題。
角色
抽象化(Abstraction): 定義抽象類的接口,并維護一個指向實現化對象的引用。通常是一個抽象類,持有 實現化的引用。
擴展抽象化(Refined Abstraction): 是抽象化角色的具體實現,它擴展了抽象化接口,并可以調用實現化對象的具體方法。
實現化(Implementor): 定義實現類的接口,這個接口不一定要與抽象化接口完全一致,它只給出實現化角色的基本操作。通常是一個接口。
具體實現化(Concrete Implementor): 是實現化角色的具體實現,實現了接口定義的操作。
思考方向
是否存在多維度變化: 如果一個類存在兩個或多個獨立變化的維度,并且這些維度都需要獨立擴展。
避免類爆炸: 當使用繼承會導致類的數量急劇增加時,橋接模式可以有效的減少類的數量,簡化系統結構。
抽象與實現是都需要獨立演化: 如果抽象和實現需要獨立的進行修改、擴展或重用。
運行時切換實現: 橋接模式允許在運行時動態的切換實現,因為抽象和實現是通過組合而不是繼承來關聯的。
橋接模式代碼練習
案例
假設需要開發一個消息通知模塊,需滿足兩個基本要求,第一個要求包含多種發送方式(如: 短信、郵件、APP內部推送),第二個要求是需要區分消息的類型(不同的類型展現形式也不同,如:緊急通知,普通通知、錯誤消息等)。
案例分析
以橋接模式為代碼開發的主要框架,以消息的類型為抽象部分,發送的方式為實現部分,使得兩個維度的變化相對獨立。
代碼
實現部分接口類
public?interface?MessageSender?{void?send(String?message);}
MessageSender
為消息發送接口類,定義實現部分的基本功能,包含一個send()
方法。
具體實現類
/***?短信發送*/
public?class?SmsSender?implements?MessageSender?{@Overridepublic?void?send(String?message)?{System.out.println("通過短信發送:?"?+?message);}}/***?郵件發送*/
public?class?EmailSender?implements?MessageSender?{@Overridepublic?void?send(String?message)?{System.out.println("通過郵件發送:?"?+?message);}}/***?APP推送*/
public?class?AppPushSender?implements?MessageSender?{@Overridepublic?void?send(String?message)?{System.out.println("通過APP推送:?"?+?message);}}
SmsSender
EmailSender
AppPushSender
實現部分的具體實現類,實現了send()
接口方法,并編寫各自發送方式。
抽象部分類
public?abstract?class?AbstractMessage?{protected?MessageSender?messageSender;protected?AbstractMessage(MessageSender?messageSender)?{this.messageSender?=?messageSender;}public?abstract?void?send(String?message);
}
AbstractMessage
為抽象部分,抽象類中包含實現部分對象的引用messageSender
,并定義了一個send()
方法,用于描述消息內容的類型。
抽象部分擴展類
/***?錯誤消息*/
public?class?ErrorMessage?extends?AbstractMessage?{protected?ErrorMessage(MessageSender?messageSender)?{super(messageSender);}@Overridepublic?void?send(String?message)?{messageSender.send("[錯誤]"?+?message?+?"?系統需要檢查!");}
}/***?普通消息*/
public?class?NormalMessage?extends?AbstractMessage?{protected?NormalMessage(MessageSender?messageSender)?{super(messageSender);}@Overridepublic?void?send(String?message)?{messageSender.send("[普通]"?+?message);}}/***?緊急消息*/
public?class?UrgentMessage?extends?AbstractMessage?{protected?UrgentMessage(MessageSender?messageSender)?{super(messageSender);}@Overridepublic?void?send(String?message)?{System.out.println("[緊急]"?+?message?+?"請立即處理!");}
}
ErrorMessage
NormalMessage
UrgentMessage
為抽象部分的擴展類,繼承了抽象部分類,并對抽象方法進行實現,定義各自消息類型的格式。
測試類
????@Testpublic?void?test_send()?{MessageSender?emailSend?=?new?EmailSender();MessageSender?smsSend?=?new?SmsSender();MessageSender?appPush?=?new?AppPushSender();AbstractMessage?normalEmail?=?new?NormalMessage(emailSend);AbstractMessage?urgentSms?=?new?UrgentMessage(smsSend);AbstractMessage?errorAppPush?=?new?ErrorMessage(appPush);normalEmail.send("系統升級通知");urgentSms.send("服務器負載過高");errorAppPush.send("數據庫連接失敗");AbstractMessage?normalSms?=?new?NormalMessage(smsSend);normalSms.send("每日報告已生成");}
運行結果
通過郵件發送:?[普通]系統升級通知
[緊急]服務器負載過高請立即處理!
通過APP推送:?[錯誤]數據庫連接失敗?系統需要檢查!
通過短信發送:?[普通]每日報告已生成Process?finished?with?exit?code?0
以上為橋接模式代碼應用的復習,加深印象。
橋接模式+策略模式
策略模式: 定義一系列的算法,并將每個算法封裝起來,使它們可以相互替換。策略模式讓算法獨立于使用它的客而變化。
案例
在一個電商平臺中,商品詳情頁需要支持多種展示方式(例如: 簡潔視圖、詳細視圖、畫廊視圖),同時為了應對不同的促銷活動,商品價格 的計算方式頁需要靈活切換(例如: 原價、打折價、會員價)。
模式職責
橋接模式: 將商品展示方式(抽象部分)與具體的商品數據渲染(實現部分)解耦。
策略模式: 封裝不同的價格計算算法,使它們可以相互替換,而不會影響客戶端。
具體設計
橋接模式
抽象部分:
ProductView
定義商品視圖的抽象類,持有對ProductRenderer
的引用擴展抽象部分:
SimpleProductView
、DeatilProductView
等具體商品視圖實現者接口:
ProductRenderer
定義商品數據渲染的接口具體實現者:
HtmlProductRenderer
、JsonProductRenderer
具體渲染器。
策略模式
抽象策略:
PriceCalculationStrategy
定義價格計算策略的接口具體策略:
OriginalPriceStrategy
、DiscountPriceStrategy
、MemberPricesStrategy
具體價格計算策略。上下文:
Product
持有對PriceCalculationStrategy
的引用,并提供設置策略的方法。
代碼
ProductRenderer 接口
/***?產品修飾類*?<p>*?橋接模式-接口部分*/
public?interface?ProductRenderer?{String?render(String?productData);
}
橋接模式中的 實現部分接口,定義了渲染商品數據的通用接口。
render(String productData)
方法接受商品數據并返回渲染后的商品介紹。
實現部分具體實現者
/***?Html?商品渲染*?<p>*?橋接模式?-?具體實現者*/
public?class?HtmlProductRenderer?implements?ProductRenderer?{@Overridepublic?String?render(String?productData)?{return?"<html><body><h1>"?+?productData?+?"</h1></body></html>";}
}/***?Json?商品渲染*/
public?class?JsonProductRenderer?implements?ProductRenderer?{@Overridepublic?String?render(String?productData)?{return?"{\"product\":\""?+?productData?+?"\"}";}
}
橋接模式中的 具體實現者,分別實現了將商品數據渲染成HTML格式和JSON格式的邏輯。
ProductView 抽象類
public?abstract?class?ProductView?{protected?ProductRenderer?productRenderer;public?ProductView(ProductRenderer?productRenderer)?{this.productRenderer?=?productRenderer;}public?void?setProductRenderer(ProductRenderer?productRenderer)?{this.productRenderer?=?productRenderer;}public?abstract?void?display(String?productData);
}
橋接模式中的抽象部分,維護一個
ProductRenderer
類型的引用,作為連接抽象和實現 的橋梁。setProductRenderer()
方法允許在運行時,動態改變渲染器。display(String productData)
抽象方法,由具體的視圖類型實現其展示邏輯。
抽象部分擴展類
/***?詳細商品視圖*?<p>*?橋接模式-擴展抽象部分*/
public?class?DetailProductView?extends?ProductView?{public?DetailProductView(ProductRenderer?productRenderer)?{super(productRenderer);}@Overridepublic?void?display(String?productData)?{System.out.println("Detail?View:?"?+?productRenderer.render("Detailed?Product?Info:?"?+?productData));}}/***?簡單視圖擴展*?<p>*?橋接模式-擴展抽象部分*/
public?class?SimpleProductView?extends?ProductView?{public?SimpleProductView(ProductRenderer?productRenderer)?{super(productRenderer);}@Overridepublic?void?display(String?productData)?{System.out.println("Simple?View:?"?+?productRenderer.render(productData));}
}
橋接模式中的抽象擴展部分,代表了不同復雜度的商品視圖。
通過調用
productRenderer.render()
方法,將實際渲染工作委托給了具體ProductRenderer
實現類。
PriceCalculationStrategy(策略抽象類)
/***?價格計算策略接口*?<p>*?策略模式-抽象策略*/
public?interface?PriceCalculationStrategy?{double?calculatePrice(double?originalPrice);}
定義了價格計算的通用接口,
calculatePrice(double originalPrice)
方法接受原始價格并返回計算后的價格。
策略實現類
/***?原始價格策略*?<p>*?策略模式-具體策略*/
public?class?OriginalPriceStrategy?implements?PriceCalculationStrategy?{@Overridepublic?double?calculatePrice(double?originalPrice)?{System.out.println("?===原始價格===?");return?originalPrice;}
}/***?會員價格策略*?<p>*?策略模式-具體策略*/
public?class?MemberPriceStrategy?implements?PriceCalculationStrategy?{/***?會員折扣價*/private?double?memberDiscount;public?MemberPriceStrategy(double?memberDiscount)?{this.memberDiscount?=?memberDiscount;}@Overridepublic?double?calculatePrice(double?originalPrice)?{System.out.println("使用會員折扣,折扣價格:?"?+?memberDiscount);return?originalPrice?-?memberDiscount;}
}/***?折扣價格策略*?<p>*?策略模式-具體策略*/
public?class?DiscountPriceStrategy?implements?PriceCalculationStrategy?{/***?折扣率*/private?double?discountRate;public?DiscountPriceStrategy(double?discountRate)?{this.discountRate?=?discountRate;}@Overridepublic?double?calculatePrice(double?originalPrice)?{System.out.println("使用折扣計算折扣價格,?折扣率:?"?+?discountRate);return?originalPrice?*?(1?-?discountRate);}
}
策略模式中的具體策略類,對
calculatePrice()
方法進行實現,填充了各自的計算邏輯和必須的參數。
Product(上下文)
public?class?Product?{private?String?name;private?double?originalPrice;private?PriceCalculationStrategy?priceStrategy;public?Product(String?name,?double?originalPrice,?PriceCalculationStrategy?priceStrategy)?{this.name?=?name;this.originalPrice?=?originalPrice;this.priceStrategy?=?priceStrategy;}public?void?setPriceStrategy(PriceCalculationStrategy?priceStrategy)?{this.priceStrategy?=?priceStrategy;}public?String?getName()?{return?name;}public?double?getCalculatedPrice()?{return?priceStrategy.calculatePrice(originalPrice);}
}
策略模式中的上下文,持有一個
PriceCalculationStrategy
類型的引用,setPriceStrategy()
方法允許在運行時動態設置價格計算策略。getCalculatedPrice()
方法 將價格計算的職責委托給當前設置的PriceCalculationStrategy
實現類。
測試類
????@Testpublic?void?test_bridge_strategy()?{Product?product?=?new?Product("Smart?Watch",?300.0,?new?OriginalPriceStrategy());SimpleProductView?productViewWithBridge?=?new?SimpleProductView(new?HtmlProductRenderer());productViewWithBridge.display(product.getName()?+?"?-?Original?Price:?"?+?product.getCalculatedPrice());product.setPriceStrategy(new?DiscountPriceStrategy(0.2));productViewWithBridge.setProductRenderer(new?JsonProductRenderer());productViewWithBridge.display(product.getName()?+?"?-?Discounted?Price:?"?+?product.getCalculatedPrice());System.out.println("\n?應用會員價格策略和HTML渲染器:?");product.setPriceStrategy(new?MemberPriceStrategy(25.0));productViewWithBridge.setProductRenderer(new?HtmlProductRenderer());productViewWithBridge.display(product.getName()?+?"-?Member?Price:?"?+?product.getCalculatedPrice());}
運行結果
?===原始價格===?
Simple?View:?<html><body><h1>Smart?Watch?-?Original?Price:?300.0</h1></body></html>
使用折扣計算折扣價格,?折扣率:?0.2
Simple?View:?{"product":"Smart?Watch?-?Discounted?Price:?240.0"}應用會員價格策略和HTML渲染器:?
使用會員折扣,折扣價格:?25.0
Simple?View:?<html><body><h1>Smart?Watch-?Member?Price:?275.0</h1></body></html>Process?finished?with?exit?code?0
組合優勢
獨立變化: 橋接模式使得商品視圖和數據渲染可以獨立變化,策略模式使得價格計算算法可以獨立變化,兩種設計模式的結合,使得系統在多個維度上都具有高度的靈活性和可擴展性。
高度復用: 渲染器和價格策略都可以被不同的商品視圖和商品對象復用。
增強擴展性: 增加新的商品視圖類型、新的數據渲染方式或新的價格計算策略,都只需要添加新的類,無需修改現有代碼。
橋接模式+抽象工廠模式
提供一個接口,用于創建相關或依賴對象的家族,而無需指定它們的具體類。
案例
ERP系統報表功能需求:
報表類型支持:銷售報表 庫存報表
導出格式支持:PDF Excel
界面主題選項:經典主題 現代主題
模式職責
橋接模式: 將報表生成邏輯與導出格式實現解耦,使得二者能夠獨立變化。
抽象工廠模式: 創建主題相關的報表和導出格式對象族(經典主題/現代主題)
具體設計
橋接模式
抽象部分:
Peport
定義報告的結構,并持有一個ExportFormat
的引用擴展抽象部分:
InventoryReport
SalesReport
分別生成庫存報表、銷售報表內容實現部分:
ExportFormat
定義export()
方法具體實現:
PDFExport
ExcelExport
分別提供基礎 PDF 、Excel 導出邏輯
抽象工廠模式
抽象工廠:
ThemeFactory
接口,定義了創建產品族的一系列方法具體工廠:
ClassicThemeFactory
ModernThemeFactory
分別創建經典主題風格 和 現代主題風格的對象抽象產品:
Peport
具體產品:
SalesReport
InventoryReport
PDFExport
ExcelExport
代碼
ExportFormat
/***?導出格式*?<p>*?橋接模式-實現接口部分*/
public?interface?ExportFormat?{String?export(String?content);}
橋接模式中的實現者接口,定義了報表導出的方法,
export(String content)
方法 接受原始內容,返回添加格式后的內容。
具體實現類
/***?PDF導出實現*/
public?class?PDFExport?implements?ExportFormat?{@Overridepublic?String?export(String?content)?{return?"生成PDF文檔:?"?+?content;}
}/***?Excel?導出實現*/
public?class?ExcelExport?implements?ExportFormat?{@Overridepublic?String?export(String?content)?{return?"生成Excel文件:?"?+?content;}}
橋接模式中的具體實現者,分別將接受的內容導出為 PDF 和 Excel
Report
/***?報表*?<p>*?橋接模式-抽象部分*/
public?abstract?class?Report?{protected?String?title;protected?List<String>?data;protected?ExportFormat?exportFormat;public?Report(ExportFormat?exportFormat)?{this.exportFormat?=?exportFormat;}public?void?setTitle(String?title)?{this.title?=?title;}public?void?setData(List<String>?data)?{this.data?=?data;}public?abstract?String?generate();public?String?export()?{return?exportFormat.export(generate());}
}
橋接模式中的抽象部分,維護一個
ExportFormat
類型的引用。generate()
方法用于獲取導出的數據內容export()
方法委托ExportFormat
實現類 增加導出的格式內容。
抽象部分擴展類
/***?庫存報表*/
public?class?InventoryReport?extends?Report?{public?InventoryReport(ExportFormat?exportFormat)?{super(exportFormat);this.title?=?"庫存報表";}@Overridepublic?String?generate()?{StringBuilder?content?=?new?StringBuilder(title?+?":\n");for?(String?item?:?data)?{content.append("?*?").append(item).append("\n");}return?content.toString();}
}/***?銷售報表*/
public?class?SalesReport?extends?Report?{public?SalesReport(ExportFormat?exportFormat)?{super(exportFormat);this.title?=?"銷售報表";}@Overridepublic?String?generate()?{StringBuilder?content?=?new?StringBuilder(title?+?":\n");for?(String?item?:?data)?{content.append("?-?").append(item).append("\n");}return?content.toString();}
}
橋接模式中的抽象部分的擴展,分別用于獲取庫存 、銷售 報表數據。
ThemeFactory(抽象工廠類)
/***?主題對象*?<p>*?抽象工廠*/
public?interface?ThemeFactory?{Report?createSalesReport();ExportFormat?createPDFExport();Report?createInventorReport();ExportFormat?createExcelExport();
}
抽象工廠模式中的抽象工廠接口,聲明創建相關產品對象的接口。
具體工廠類
/***?經典主題風格*?<p>*?具體工廠*/
public?class?ClassicThemeFactory?implements?ThemeFactory?{@Overridepublic?Report?createSalesReport()?{return?new?SalesReport(createPDFExport());}@Overridepublic?ExportFormat?createPDFExport()?{return?new?PDFExport()?{@Overridepublic?String?export(String?content)?{return?"經典主題?-?"?+?super.export(content)?+?"?(帶傳統邊框)";}};}@Overridepublic?Report?createInventorReport()?{return?new?InventoryReport(createExcelExport());}@Overridepublic?ExportFormat?createExcelExport()?{return?new?ExcelExport()?{@Overridepublic?String?export(String?content)?{return?"經典主題?-?"?+?super.export(content)?+?"?(使用傳統顏色)";}};}
}/***?現代主題風格*?<p>*?具體工廠*/
public?class?ModernThemeFactory?implements?ThemeFactory?{@Overridepublic?Report?createSalesReport()?{return?new?SalesReport(createPDFExport());}@Overridepublic?ExportFormat?createPDFExport()?{return?new?PDFExport()?{@Overridepublic?String?export(String?content)?{return?"現代主題?-?"?+?super.export(content)?+?"?(簡約設計)";}};}@Overridepublic?Report?createInventorReport()?{return?new?InventoryReport(createExcelExport());}@Overridepublic?ExportFormat?createExcelExport()?{return?new?ExcelExport()?{@Overridepublic?String?export(String?content)?{return?"現代主題?-?"?+?super.export(content)?+?"?(扁平化風格)";}};}
}
抽象工廠模式中的具體工廠類,分別用于創建經典主題和現代主題風格的導出格式對象族,遵循抽象工廠模式,創建統一風格的主題產品。
測試類
????@Testpublic?void?test_report()?{List<String>?salesData?=?Arrays.asList("產品A:?100件",?"產品B:?200件",?"產品C:?150件");List<String>?inventoryData?=?Arrays.asList("倉庫1:?500件",?"倉庫2:?300件",?"倉庫3:?450件");System.out.println("?===?ERP系統報表模塊?===");System.out.println();//?使用經典主題System.out.println("【經典主題報表】");ThemeFactory?classicFactory?=?new?ClassicThemeFactory();Report?classicFactorySalesReport?=?classicFactory.createSalesReport();classicFactorySalesReport.setData(salesData);System.out.println(classicFactorySalesReport.export());System.out.println();//?使用現代主題System.out.println("【現代主題報表】");ThemeFactory?modernFactory?=?new?ModernThemeFactory();//?現代訂單報表Report?modernFactorySalesReport?=?modernFactory.createSalesReport();modernFactorySalesReport.setData(salesData);System.out.println(modernFactorySalesReport.export());//?現代庫存報表Report?modernFactoryInventorReport?=?modernFactory.createInventorReport();modernFactoryInventorReport.setData(inventoryData);System.out.println(modernFactoryInventorReport.export());}
運行結果
?===?ERP系統報表模塊?===【經典主題報表】
經典主題?-?生成PDF文檔:?銷售報表:-?產品A:?100件-?產品B:?200件-?產品C:?150件(帶傳統邊框)【現代主題報表】
現代主題?-?生成PDF文檔:?銷售報表:-?產品A:?100件-?產品B:?200件-?產品C:?150件(簡約設計)
現代主題?-?生成Excel文件:?庫存報表:*?倉庫1:?500件*?倉庫2:?300件*?倉庫3:?450件(扁平化風格)Process?finished?with?exit?code?0
組合優勢
高度的靈活性: 新增報表類型或導出格式時,可以直接新增對應實現類,無需修改現有代碼。新增主圖變化擴展
ThemeFactory
,新增報表邏輯擴展Report
,新增導出格式則擴展ExportFormat
。風格的統一性: 每個工廠確保創建的對象屬于同一個主題
--
未完,下一篇接著寫,代碼量比較多。