一、概述
簡介
設計模式(Design Pattern)是前輩們對代碼開發經驗的總結,它不是語法規定,是解決特定問題的一系列思想,是面向對象設計原則的具象化實現, 是解決 “需求變更” 與 “系統複雜度” 矛盾的標準化方案 —— 並非孤立的 “代碼模板”,而是 “高內聚、低耦合” 思想的落地工具。其核心價值在於提升代碼的可複用性、可維護性、可讀性、穩健性及安全性。
1994 年,GoF(Gang of Four:Erich Gamma、Richard Helm、Ralph Johnson、John Vlissides)合著的《Design Patterns - Elements of Reusable Object-Oriented Software》(中文譯名《設計模式 - 可複用的面向對象軟件元素》)出版,收錄 23 種經典設計模式,奠定該領域的行業標準,即 “GoF 設計模式”。
核心思想
- 對接口編程,而非對實現編程
- 優先使用對象組合,而非繼承
- 靈活適配需求:簡單程序無需過度設計,大型項目 / 框架必須藉助模式優化架構
組件生命週期
| 模式類型 | 核心關注點 | 生命週期階段 | 代表模式 |
|---|---|---|---|
| 創建型模式 | 對象創建機制 (解耦創建與使用) | 組件的創建 | 單例、工廠方法、抽象工廠、原型、建造者 |
| 結構型模式 | 對象 / 類的組合方式 | 組件的使用 | 代理、適配器、裝飾器、外觀、享元、橋接、組合、過濾器 |
| 行為型模式 | 對象 / 類的運行時協作流程 | 組件的交互與銷燬 | 策略、觀察者、責任鏈、模板方法、命令、狀態、中介者、迭代器、訪問者、備忘錄、解釋器 |
七大設計原則
| 原則名稱 | 核心定義 | 關聯模式 | 實際開發決策邏輯 |
|---|---|---|---|
| 開閉原則(OCP) | 對擴展開放,對修改關閉 (新增功能通過擴展類實現,不修改原有代碼) | 所有模式的終極目標 | 新增需求優先考慮 “加類”,而非 “改類” |
| 依賴倒轉原則(DIP) | 依賴抽象而非具體實現 (面向接口編程,不依賴具體類) | 工廠、策略、橋接 | 類的依賴通過接口注入,而非直接 new 具體類 |
| 合成複用原則(CRP) | 優先使用組合 / 聚合,而非繼承 (降低耦合,提升靈活性) | 裝飾器、組合、橋接 | 複用功能時,先考慮 “組合”,再考慮 “繼承” |
| 單一職責原則(SRP) | 一個類僅負責一項核心職責 (避免 “萬能類”) | 策略、適配器、裝飾器 | 當一個類有多個修改原因時,立即拆分 |
| 接口隔離原則(ISP) | 使用多個專用接口替代單一萬能接口 (降低類與接口的耦合) | 適配器、代理 | 接口方法拆分到 “最小粒度”,避免實現類冗餘 |
| 里氏代換原則(LSP) | 子類可替換父類,且不破壞原有邏輯 (繼承複用的核心前提) | 模板方法、策略 | 子類重寫父類方法時,不能改變父類契約 |
| 迪米特法則(LOD) | 實體應儘量少與其他實體直接交互 (通過中間者解耦) | 中介者、外觀、責任鏈 | 兩個無直接關聯的類,通過第三方間接交互 |
二、原理與框架應用
創建型模式
為什麼用創建型模式?
- 創建型模式關注點“怎樣創建出對象?”“將對象的創建與使用分離”
- 降低系統的耦合度
- 使用者無需關注對象的創建細節
- 對象的創建由相關的工廠來完成;(各種工廠模式)
- 對象的創建由一個建造者來完成;(建造者模式)
- 對象的創建由原來對象克隆完成;(原型模式)
- 對象始終在系統中只有一個實例;(單例模式)
創建型模式之單例模式
單例模式(Singleton Pattern)是 Java 中最簡單的設計模式之一,提供了一種創建對象的最佳方式。這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。這個類提供了一種訪問其唯一的對象的方式,可以直接訪問,不需要實例化該類的對象。
意圖: 保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
主要解決: 一個全局使用的類頻繁地創建與銷燬。
何時使用: 當您想控制實例數目,節省系統資源的時候。
如何解決: 判斷系統是否已經有這個單例,如果有則返回,如果沒有則創建。
優點:
1、在內存裏只有一個實例,減少了內存的開銷,尤其是頻繁的創建和銷燬實例(比如首頁頁面緩存)。
2、避免對資源的多重佔用(比如寫文件操作)。
缺點:
沒有接口,不能繼承,與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來實例化。
使用場景:
1、要求生產唯一序列號。
2、多線程中的線程池。
3、創建的一個對象需要消耗的資源過多,比如 I/O 與數據庫的連接等。
4、系統環境信息(System.getProperties())。
單例模式四種實現方案
餓漢式
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 餓漢式單例(線程安全)
* 核心原理:依賴類加載機制(JVM保證類初始化時線程安全)
* 適用場景:實例佔用資源小、啓動時初始化可接受的場景
*/
public class LibifuTestSingleton {
private static final Logger log = LoggerFactory.getLogger(LibifuTestSingleton.class);
// 類加載時直接初始化實例(無延遲加載)
private static final LibifuTestSingleton INSTANCE = new LibifuTestSingleton();
// 私有構造器(禁止外部實例化)
private LibifuTestSingleton() {
log.info("LibifuTestSingleton 實例初始化完成");
}
// 全局訪問點(無鎖,高效)
public static LibifuTestSingleton getInstance() {
return INSTANCE;
}
// 業務方法示例
public void doBusiness() {
log.info("餓漢式單例(LibifuTestSingleton)執行業務邏輯");
}
}
懶漢式
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 懶漢式單例(線程安全)
* 核心原理:第一次調用時初始化,synchronized保證線程安全
* 適用場景:實例使用頻率極低、無性能要求的場景
*/
public class LibifuTestLazySingleton {
private static final Logger log = LoggerFactory.getLogger(LibifuTestLazySingleton.class);
// 私有靜態實例(初始為null,延遲加載)
private static LibifuTestLazySingleton instance;
// 私有構造器(禁止外部實例化)
private LibifuTestLazySingleton() {
log.info("LibifuTestLazySingleton 實例初始化完成");
}
// 同步方法(保證多線程下唯一實例)
public static synchronized LibifuTestLazySingleton getInstance() {
if (instance == null) {
instance = new LibifuTestLazySingleton();
}
return instance;
}
// 業務方法示例
public void doBusiness() {
log.info("懶漢式單例(LibifuTestLazySingleton)執行業務邏輯");
}
}
雙檢鎖 (DCL,JDK1.5+)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 雙檢鎖單例(線程安全,高效)
* 核心原理:volatile禁止指令重排序,雙重校驗+類鎖保證唯一性
* 適用場景:大多數高併發場景
*/
public class LibifuTestDclSingleton {
private static final Logger log = LoggerFactory.getLogger(LibifuTestDclSingleton.class);
// volatile關鍵字:禁止instance = new LibifuTestDclSingleton()指令重排序
private volatile static LibifuTestDclSingleton instance;
// 私有構造器(禁止外部實例化,含防反射攻擊)
private LibifuTestDclSingleton() {
log.info("LibifuTestDclSingleton 實例初始化完成");
// 防反射攻擊:若實例已存在,直接拋出異常
if (instance != null) {
throw new IllegalStateException("單例實例已存在,禁止重複創建");
}
}
// 全局訪問點(雙重校驗+類鎖,兼顧線程安全與效率)
public static LibifuTestDclSingleton getInstance() {
// 第一次校驗:避免頻繁加鎖(提高效率)
if (instance == null) {
// 類鎖:保證同一時刻只有一個線程進入實例創建邏輯
synchronized (LibifuTestDclSingleton.class) {
// 第二次校驗:確保唯一實例(防止多線程併發繞過第一次校驗)
if (instance == null) {
instance = new LibifuTestDclSingleton();
}
}
}
return instance;
}
// 防序列化漏洞:反序列化時返回已有實例(而非創建新實例)
private Object readResolve() {
return getInstance();
}
// 業務方法示例
public void doBusiness() {
log.info("雙檢鎖單例(LibifuTestDclSingleton)執行業務邏輯");
}
}
枚舉單例(JDK1.5+)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 枚舉單例(天然線程安全、防反射、防序列化)
* 核心原理:枚舉類的實例由JVM管理,天然唯一
* 適用場景:安全性要求極高的場景(如配置中心、加密工具類)
*/
public enum LibifuTestEnumSingleton {
INSTANCE;
private static final Logger log = LoggerFactory.getLogger(LibifuTestEnumSingleton.class);
// 枚舉構造器(默認私有,無需顯式聲明)
LibifuTestEnumSingleton() {
log.info("LibifuTestEnumSingleton 實例初始化完成");
}
// 業務方法示例
public void doBusiness() {
log.info("枚舉單例(LibifuTestEnumSingleton)執行業務邏輯");
}
}
框架應用
Spring 框架中 Bean 默認作用域為singleton(單例),核心通過AbstractBeanFactory類的緩存機制 + 單例創建邏輯實現 —— 確保每個 Bean 在 Spring 容器中僅存在一個實例,且由容器統一管理創建、緩存與銷燬,降低對象頻繁創建銷燬的資源開銷,契合單例模式 “唯一實例 + 全局訪問” 的核心思想。
核心邏輯:Bean 創建後存入singletonObjects(單例緩存池),後續獲取時優先從緩存讀取,未命中則觸發創建流程,同時通過同步機制保證多線程安全。
以下選取AbstractBeanFactory中實現單例 Bean 獲取的核心代碼片段:
// 1. 對外暴露的獲取Bean的公共接口,接收Bean名稱參數
@Override
public Object getBean(String name) throws BeansException {
// 2. 委託doGetBean方法實現具體邏輯,參數分別為:Bean名稱、所需類型(null表示不指定)、構造參數(null)、是否僅類型檢查(false)
return doGetBean(name, null, null, false);
}
// 3. 核心獲取Bean的實現方法,泛型T保證類型安全
@SuppressWarnings("unchecked")
protected <T> T doGetBean(
String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly) throws BeansException {
// 4. 處理Bean名稱:轉換別名、去除FactoryBean前綴(如&),得到原始Bean名稱
String beanName = transformedBeanName(name);
// 5. 從單例緩存中獲取Bean實例(核心:優先複用已有實例)
Object sharedInstance = getSingleton(beanName);
// 6. 緩存命中(存在單例實例)且無構造參數(無需重新創建)
if (sharedInstance != null && args == null) {
// 7. 處理特殊Bean(如FactoryBean):如果是FactoryBean,返回其getObject()創建的實例,而非FactoryBean本身
T bean = (T) getObjectForBeanInstance(sharedInstance, name, beanName, null);
} else {
// 8. 緩存未命中或需創建新實例(非單例、原型等作用域)的邏輯(此處省略,聚焦單例)
}
// 9. 返回最終的Bean實例(類型轉換後)
return (T) bean;
}
// 10. 從單例緩存中獲取實例的核心方法,allowEarlyReference表示是否允許早期引用(循環依賴場景)
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 11. 從一級緩存(singletonObjects)獲取已完全初始化的單例實例(key=Bean名稱,value=Bean實例)
Object singletonObject = this.singletonObjects.get(beanName);
// 12. 緩存未命中,且當前Bean正在創建中(解決循環依賴)
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
// 13. 對一級緩存加鎖,保證多線程安全(避免併發創建多個實例)
synchronized (this.singletonObjects) {
// 14. 從二級緩存(earlySingletonObjects)獲取早期暴露的實例(未完全初始化,僅解決循環依賴)
singletonObject = this.earlySingletonObjects.get(beanName);
// 15. 二級緩存未命中,且允許早期引用
if (singletonObject == null && allowEarlyReference) {
// 16. 從三級緩存(singletonFactories)獲取Bean的工廠對象(用於創建早期實例)
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
// 17. 工廠對象存在,通過工廠創建早期實例
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 18. 將早期實例存入二級緩存,同時移除三級緩存(避免重複創建)
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
// 19. 返回單例實例(可能是完全初始化的,也可能是早期實例)
return singletonObject;
}
入口: getBean(String name)是獲取 Bean 的入口,委託doGetBean實現細節;
名稱處理: transformedBeanName統一 Bean 名稱格式,避免別名、FactoryBean 前綴導致的識別問題;
緩存優先: 通過getSingleton從三級緩存(singletonObjects→earlySingletonObjects→singletonFactories)獲取實例,優先複用已有實例,契合單例模式核心;
線程安全: 對單例緩存加鎖,防止多線程併發創建多個實例;
特殊處理: getObjectForBeanInstance區分普通 Bean 和 FactoryBean,確保返回用户預期的實例。
整個流程圍繞 “緩存複用 + 安全創建” 實現 Spring 單例 Bean 的管理,是單例模式在框架級的經典落地。
結構型模式
為什麼用結構型模式?
- 結構型模式關注點“怎樣組合對象/類”
- 類結構型模式關心類的組合,由多個類可以組合成一個更大的(繼承)
- 對象結構型模式關心類與對象的組合,通過關聯關係在一個類中定義另一個類的實例對象(組合)根據“合成複用原則”,在系統中儘量使用關聯關係來替代繼承關係,因此大部分結構型模式都是對象結構型模式。
- 適配器模式(Adapter Pattern):兩個不兼容接口之間適配的橋樑
- 橋接模式(Bridge Pattern):相同功能抽象化與實現化解耦,抽象與實現可以獨立升級
- 過濾器模式(Filter、Criteria Pattern):使用不同的標準來過濾一組對象
- 組合模式(Composite Pattern):相似對象進行組合,形成樹形結構
- 裝飾器模式(Decorator Pattern):向一個現有的對象添加新的功能,同時又不改變其結構
- 外觀模式(Facade Pattern):向現有的系統添加一個接口,客户端訪問此接口來隱藏系統的複雜性
- 享元模式(Flyweight Pattern):嘗試重用現有的同類對象,如果未找到匹配的對象,則創建新對象
- 代理模式(Proxy Pattern):一個類代表另一個類的功能
結構型模式之外觀模式
外觀模式(Facade Pattern)為複雜子系統提供統一高層接口,隱藏內部複雜性,簡化客户端調用。這種模式涉及到一個單一的類,該類提供了客户端請求的簡化方法和對現有系統類方法的委託調用。
意圖: 為子系統中的一組接口提供一個一致的界面,外觀模式定義了一個高層接口,這個接口使得這一子系統更加容易使用。
主要解決: 降低訪問複雜系統的內部子系統時的複雜度,簡化客户端之間的接口。
何時使用:
1、客户端不需要知道系統內部的複雜聯繫,整個系統只需提供一個"接待員"即可。
2、定義系統的入口。
如何解決: 客户端不與系統耦合,外觀類與系統耦合。
優點:
1、減少系統相互依賴。
2、提高靈活性。
3、提高了安全性。
缺點:
不符合開閉原則,如果要改東西很麻煩,繼承重寫都不合適。
使用場景:
1、JAVA 的三層開發模式
2、分佈式系統的網關
外觀模式簡單應用
程序員這行,主打一個 “代碼虐我千百遍,我待鍵盤如初戀”—— 白天 debug ,深夜改 Bug ,免疫力堪比未加 try-catch 的代碼,説崩就崩。現在醫院就診(掛號、繳費、取藥等子系統)都是通過 “微信自助程序”來統一入口,下面就使用外觀模式簡單實現:
子系統組件(就診各窗口)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 子系統1:掛號窗口
*/
public class LibifuTestRegisterWindow {
private static final Logger log = LoggerFactory.getLogger(LibifuTestRegisterWindow.class);
/**
* 掛號業務邏輯
* @param name 患者姓名
* @param department 就診科室
*/
public void register(String name, String department) {
log.info(" {} 已完成{}掛號,掛號成功", name, department);
}
}
/**
* 子系統2:醫保繳費窗口
*/
public class LibifuTestPaymentWindow {
private static final Logger log = LoggerFactory.getLogger(LibifuTestPaymentWindow.class);
/**
* 醫保結算業務邏輯
* @param name 患者姓名
* @param amount 繳費金額(元)
*/
public void socialInsuranceSettlement(String name, double amount) {
log.info("{} 醫保結算完成,繳費金額:{}元", name, amount);
}
}
/**
* 子系統3:取藥窗口
*/
public class LibifuTestDrugWindow {
private static final Logger log = LoggerFactory.getLogger(LibifuTestDrugWindow.class);
/**
* 取藥業務邏輯
* @param name 患者姓名
* @param drugNames 藥品名稱列表
*/
public void takeDrug(String name, String... drugNames) {
String drugs = String.join("、", drugNames);
log.info("{} 已領取藥品:{},取藥完成", name, drugs);
}
}
外觀類(微信自助程序)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 外觀類:微信自助程序(統一就診入口)
*/
public class LibifuTestWeixinHospitalFacade {
private static final Logger log = LoggerFactory.getLogger(LibifuTestWeixinHospitalFacade.class);
// 依賴子系統組件(外觀類與子系統耦合,客户端與子系統解耦)
private final LibifuTestRegisterWindow registerWindow;
private final LibifuTestPaymentWindow paymentWindow;
private final LibifuTestDrugWindow drugWindow;
// 構造器初始化子系統(也可通過依賴注入實現)
public LibifuTestWeixinHospitalFacade() {
this.registerWindow = new LibifuTestRegisterWindow();
this.paymentWindow = new LibifuTestPaymentWindow();
this.drugWindow = new LibifuTestDrugWindow();
}
/**
* 統一就診流程(封裝子系統調用,對外暴露單一接口)
* @param name 患者姓名
* @param department 就診科室
* @param amount 繳費金額
* @param drugNames 藥品名稱
*/
public void processMedicalService(String name, String department, double amount, String... drugNames) {
log.info("\n===== {} 發起微信自助就診流程 =====", name);
try {
// 1. 調用掛號子系統
registerWindow.register(name, department);
// 2. 調用醫保繳費子系統
paymentWindow.socialInsuranceSettlement(name, amount);
// 3. 調用取藥子系統
drugWindow.takeDrug(name, drugNames);
log.info("===== {} 就診流程全部完成 =====", name);
} catch (Exception e) {
log.error("===== {} 就診流程失敗 =====", name, e);
throw new RuntimeException("就診流程異常,請重試", e);
}
}
}
測試類
/**
* 客户端:測試外觀模式調用
*/
public class LibifuTestFacadeClient {
public static void main(String[] args) {
// 1. 獲取外觀類實例(僅需與外觀類交互)
LibifuTestWeixinHospitalFacade weixinFacade = new LibifuTestWeixinHospitalFacade();
// 2. 調用統一接口,完成就診全流程(無需關注子系統細節)
weixinFacade.processMedicalService(
"libifu",
"呼吸內科",
198.5,
"布洛芬緩釋膠囊", "感冒靈顆粒"
);
}
}
運行結果
框架應用
Spring 框架中外觀模式(Facade Pattern) 最經典的落地是 ApplicationContext 接口及其實現類。
ApplicationContext 作為「外觀類」,封裝了底層多個複雜子系統:
- BeanFactory(Bean 創建 / 管理核心);
- ResourceLoader(配置文件 / 資源加載);
- ApplicationEventPublisher(事件發佈);
- MessageSource(國際化消息處理);
- EnvironmentCapable(環境變量 / 配置解析)。
開發者無需關注這些子系統的交互細節,僅通過 ApplicationContext 提供的統一接口(如 getBean()、publishEvent())即可完成 Spring 容器的所有核心操作 —— 就像程序員通過「微信自助程序」看病,不用關心醫院內部掛號 / 繳費 / 取藥的流程,只調用統一入口即可,這正是外觀模式「簡化複雜系統交互」的核心價值。
以下選取ApplicationContext 、AbstractApplicationContext核心代碼片段,展示外觀模式的落地邏輯:
package org.springframework.context;
import org.springframework.beans.factory.HierarchicalBeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.core.env.EnvironmentCapable;
import org.springframework.core.io.support.ResourcePatternResolver;
/**
* 外觀接口:整合多個子系統接口,提供統一的容器操作入口
*/
public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory,
HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {
// 1. 獲取應用上下文唯一ID(封裝底層無,僅統一暴露)
String getId();
// 2. 獲取應用名稱(統一接口)
String getApplicationName();
// 3. 獲取上下文顯示名稱(統一接口)
String getDisplayName();
// 4. 獲取上下文首次加載的時間戳(統一接口)
long getStartupDate();
// 5. 獲取父上下文(封裝層級BeanFactory的父容器邏輯)
ApplicationContext getParent();
// 6. 獲取自動裝配BeanFactory(封裝底層BeanFactory的自動裝配能力,核心子系統入口)
AutowireCapableBeanFactory getAutowireCapableBeanFactory() throws IllegalStateException;
}
package org.springframework.context.support;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import java.util.concurrent.atomic.AtomicBoolean;
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
// ========== 核心1:refresh() - 封裝所有子系統的初始化邏輯 ==========
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// 1. 封裝子系統初始化前置檢查
prepareRefresh();
// 2. 封裝BeanFactory子系統的創建/刷新(子類實現具體BeanFactory,如DefaultListableBeanFactory)
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 3. 封裝BeanFactory子系統的基礎配置
prepareBeanFactory(beanFactory);
try {
// xxx 其他源碼省略
// 4. 封裝BeanFactory後置處理器執行、事件系統初始化、單例Bean初始化等所有子系統邏輯
finishBeanFactoryInitialization(beanFactory);
// 5. 封裝容器激活、刷新完成事件發佈(子系統收尾)
finishRefresh();
} catch (BeansException ex) {
// 6. 封裝子系統初始化失敗的回滾邏輯
}
}
}
// ========== 核心2:getBean() - 封裝BeanFactory子系統的調用 + 狀態檢查 ==========
@Override
public <T> T getBean(Class<T> requiredType) throws BeansException {
// 外觀層封裝:子系統狀態檢查(客户端無需關注BeanFactory是否活躍)
assertBeanFactoryActive();
// 外觀層委託:調用底層BeanFactory子系統的getBean,客户端無需關注BeanFactory具體實現
return getBeanFactory().getBean(requiredType);
}
// ========== 抽象方法:委託子類實現具體BeanFactory獲取(屏蔽子系統實現) ==========
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
}
Spring 通過 ApplicationContext(外觀接口)和 AbstractApplicationContext(外觀實現)封裝了其他子系統的複雜邏輯:
- 客户端只需調用 ApplicationContext.getBean() 即可獲取 Bean,無需關注底層 Bean 的緩存、實例化、狀態檢查等細節;
- 外觀類屏蔽了子系統的複雜度,降低了客户端與底層 BeanFactory 的耦合,符合外觀模式的設計思想。
行為型模式
為什麼用行為型模式?
- 行為型模式關注點“怎樣運行對象/類”關注類/對象的運行時流程控制。
- 行為型模式用於描述程序在運行時複雜的流程控制,描述多個類或對象之間怎樣相互協作共同完成單個對象都無法單獨完成的任務,它涉及算法與對象間職責的分配。
- 行為型模式分為類行為模式和對象行為模式,前者採用繼承機制來在類間分派行為後者採用組合或聚合在對象間分配行為。由於組合關係或聚合關係比繼承關係耦合度低,滿足“合成複用原則”,所以對象行為模式比類行為模式具有更大的靈活性。
- 模板方法(Template Method)模式:父類定義算法骨架,某些實現放在子類
- 策略(Strategy)模式:每種算法獨立封裝,根據不同情況使用不同算法策略
- 狀態(State)模式:每種狀態獨立封裝,不同狀態內部封裝了不同行為
- 命令(Command)模式:將一個請求封裝為一個對象,使發出請求的責任和執行請求的責任分割開
- 責任鏈(Chain of Responsibility)模式:所有處理者封裝為鏈式結構,依次調用
- 備忘錄(Memento)模式:把核心信息抽取出來,可以進行保存
- 解釋器(Interpreter)模式:定義語法解析規則
- 觀察者(Observer)模式:維護多個觀察者依賴,狀態變化通知所有觀察者
- 中介者(Mediator)模式:取消類/對象的直接調用關係,使用中介者維護
- 迭代器(Iterator)模式:定義集合數據的遍歷規則
- 訪問者(Visitor)模式:分離對象結構,與元素的執行算法
除了模板方法模式和解釋器模式是類行為型模式,其他的全部屬於對象行為型模式。
行為型模式之策略模式
策略模式(Strategy Pattern)指的是一個類的行為或其算法可以在運行時更改,在策略模式中,我們創建表示各種策略的對象和一個行為隨着策略對象改變而改變的 context 對象,策略對象改變 context 對象的執行算法。
意圖: 定義一系列的算法,把它們一個個封裝起來, 並且使它們可相互替換。
主要解決: 在有多種算法相似的情況下,使用 if...else 所帶來的複雜和難以維護。
何時使用: 一個系統有許多許多類,而區分它們的只是它們之間的行為。
如何解決: 將這些算法封裝成一個一個的類,任意地替換。
優點:
1、算法可以自由切換。
2、避免使用多重條件判斷。
3、擴展性良好。
缺點:
1、策略類會增多。
2、所有策略類都需要對外暴露。
使用場景:
1、如果在一個系統裏面有許多類,它們之間的區別僅在於它們的行為,那麼使用策略模式可以
動態地讓一個對象在許多行為中選擇一種行為。
2、一個系統需要動態地在幾種算法中選擇一種。
3、線程池拒絕策略。
策略模式簡單應用
在電商支付系統中,都會支持多種支付方式(微信、支付寶、銀聯),每種支付方式對應一種 “支付策略”,客户端可根據用户選擇動態切換策略,無需修改支付核心邏輯,下面就使用策略模式簡單實現:
策略接口(定義統一算法規範)
/**
* 策略接口:支付策略(定義所有支付方式的統一規範)
*/
public interface LibifuTestPaymentStrategy {
/**
* 執行支付邏輯
* @param amount 支付金額(元)
* @param orderId 訂單ID
* @return 支付結果(成功/失敗)
*/
String pay(double amount, String orderId);
}
具體策略類 1:微信支付
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 具體策略:微信支付(實現支付策略接口)
*/
public class LibifuTestWechatPayStrategy implements LibifuTestPaymentStrategy {
private static final Logger log = LoggerFactory.getLogger(LibifuTestWechatPayStrategy.class);
@Override
public String pay(double amount, String orderId) {
log.info("【微信支付】開始處理訂單:{},金額:{}元", orderId, amount);
// 模擬微信支付核心邏輯(簽名、調用微信接口等)
boolean isSuccess = true; // 模擬支付成功
if (isSuccess) {
String result = String.format("【微信支付】訂單%s支付成功,金額:%.2f元", orderId, amount);
log.info(result);
return result;
} else {
String result = String.format("【微信支付】訂單%s支付失敗", orderId);
log.error(result);
return result;
}
}
}
具體策略類 2:支付寶支付
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 具體策略:支付寶支付(實現支付策略接口)
*/
public class LibifuTestAlipayStrategy implements LibifuTestPaymentStrategy {
private static final Logger log = LoggerFactory.getLogger(LibifuTestAlipayStrategy.class);
@Override
public String pay(double amount, String orderId) {
log.info("【支付寶支付】開始處理訂單:{},金額:{}元", orderId, amount);
// 模擬支付寶支付核心邏輯(驗籤、調用支付寶接口等)
boolean isSuccess = true; // 模擬支付成功
if (isSuccess) {
String result = String.format("【支付寶支付】訂單%s支付成功,金額:%.2f元", orderId, amount);
log.info(result);
return result;
} else {
String result = String.format("【支付寶支付】訂單%s支付失敗", orderId);
log.error(result);
return result;
}
}
}
具體策略類 3:銀聯支付
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 具體策略:銀聯支付(實現支付策略接口)
*/
public class LibifuTestUnionPayStrategy implements LibifuTestPaymentStrategy {
private static final Logger log = LoggerFactory.getLogger(LibifuTestUnionPayStrategy.class);
@Override
public String pay(double amount, String orderId) {
log.info("【銀聯支付】開始處理訂單:{},金額:{}元", orderId, amount);
// 模擬銀聯支付核心邏輯(加密、調用銀聯接口等)
boolean isSuccess = true; // 模擬支付成功
if (isSuccess) {
String result = String.format("【銀聯支付】訂單%s支付成功,金額:%.2f元", orderId, amount);
log.info(result);
return result;
} else {
String result = String.format("【銀聯支付】訂單%s支付失敗", orderId);
log.error(result);
return result;
}
}
}
上下文類(封裝策略調用,屏蔽算法細節)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 上下文類:支付上下文(持有策略對象,提供統一調用入口)
* 作用:客户端僅與上下文交互,無需直接操作具體策略
*/
public class LibifuTestPaymentContext {
private static final Logger log = LoggerFactory.getLogger(LibifuTestPaymentContext.class);
// 持有策略對象(可動態替換)
private LibifuTestPaymentStrategy paymentStrategy;
/**
* 構造器:初始化支付策略
* @param paymentStrategy 具體支付策略
*/
public LibifuTestPaymentContext(LibifuTestPaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
/**
* 動態切換支付策略
* @param paymentStrategy 新的支付策略
*/
public void setPaymentStrategy(LibifuTestPaymentStrategy paymentStrategy) {
log.info("【支付上下文】切換支付策略:{}", paymentStrategy.getClass().getSimpleName());
this.paymentStrategy = paymentStrategy;
}
/**
* 統一支付入口(屏蔽策略細節,對外暴露簡潔方法)
* @param amount 支付金額
* @param orderId 訂單ID
* @return 支付結果
*/
public String executePay(double amount, String orderId) {
log.info("【支付上下文】開始處理訂單{}的支付請求", orderId);
return paymentStrategy.pay(amount, orderId);
}
}
測試類
/**
* 客户端:測試策略模式(動態切換支付方式)
*/
public class LibifuTestStrategyClient {
public static void main(String[] args) {
// 1. 訂單信息
String orderId = "ORDER_20251213_001";
double amount = 199.99;
// 2. 選擇微信支付策略
LibifuTestPaymentContext paymentContext = new LibifuTestPaymentContext(new LibifuTestWechatPayStrategy());
String wechatResult = paymentContext.executePay(amount, orderId);
System.out.println(wechatResult);
// 3. 動態切換為支付寶支付策略
paymentContext.setPaymentStrategy(new LibifuTestAlipayStrategy());
String alipayResult = paymentContext.executePay(amount, orderId);
System.out.println(alipayResult);
// 4. 動態切換為銀聯支付策略
paymentContext.setPaymentStrategy(new LibifuTestUnionPayStrategy());
String unionPayResult = paymentContext.executePay(amount, orderId);
System.out.println(unionPayResult);
}
}
運行結果
框架應用
在Spring 中 ,ResourceLoader 接口及實現類是策略模式的典型落地:
- 策略接口:ResourceLoader(定義 “加載資源” 的統一規範);
- 具體策略:DefaultResourceLoader(默認資源加載)、FileSystemResourceLoader(文件系統加載)、ClassPathXmlApplicationContext(類路徑加載)等;
- 核心價值:不同資源(類路徑、文件系統、URL)的加載邏輯封裝為獨立策略,可靈活切換且不影響調用方。
- 以下選取ResourceLoader 、FileSystemResourceLoader核心代碼片段,展示策略模式的落地邏輯:
package org.springframework.core.io;
import org.springframework.lang.Nullable;
/**
* 策略接口:定義資源加載的統一規範(策略模式核心接口)
*/
public interface ResourceLoader {
// 類路徑資源前綴(常量,子系統細節)
String CLASSPATH_URL_PREFIX = "classpath:";
/**
* 策略核心方法:根據資源路徑加載Resource(所有具體策略需實現此方法)
* @param location 資源路徑(如classpath:application.xml、file:/data/config.xml)
* @return 封裝後的Resource對象
*/
Resource getResource(String location);
/**
* 輔助方法:獲取類加載器(策略實現時依賴)
*/
@Nullable
ClassLoader getClassLoader();
}
package org.springframework.core.io;
/**
* 具體策略:文件系統資源加載器(覆蓋擴展點實現文件系統加載)
*/
public class FileSystemResourceLoader extends DefaultResourceLoader {
/**
* 覆蓋策略擴展點:實現文件系統路徑加載
*/
@Override
protected Resource getResourceByPath(String path) {
// 若路徑為絕對路徑,直接創建FileSystemResource
if (path.startsWith("/")) {
return new FileSystemResource(path);
}
// 否則創建文件系統上下文資源(支持相對路徑)
else {
return new FileSystemContextResource(path);
}
}
/**
* 內部類:文件系統上下文資源(策略輔助實現)
*/
private static class FileSystemContextResource extends FileSystemResource {
public FileSystemContextResource(String path) {
super(path);
}
// xxx
}
}
| 角色 | 類 / 接口 | 作用 |
|---|---|---|
| 策略接口 | ResourceLoader | 定義getResource統一加載規範,屏蔽不同資源加載的細節 |
| 抽象策略 | DefaultResourceLoader | 實現通用加載邏輯(類路徑、URL),提供擴展點getResourceByPath |
| 具體策略 | FileSystemResourceLoader | 覆蓋擴展點,實現文件系統資源加載的專屬邏輯 |
| 調用方 | ApplicationContext(如ClassPathXmlApplicationContext) | 依賴ResourceLoader接口,無需關注具體加載策略,可靈活切換 |
三、實戰
背景
除了大家熟悉的"出價還價"列表外,現在訂單列表、"想要"收藏列表等場景也能看到心儀商品的還價信息——還價功能,在用户體驗上逐步從單一場景向多場景持續演進。
1.0 版本:
在功能初期,我們採用輕量級的設計思路:
- 聚焦核心場景:僅在還價列表頁提供精簡高效的還價服務
- 極簡技術實現:通過線性調用商品/庫存/訂單等等服務,確保功能穩定交付
- 智能引導策略:內置還價優先級算法,幫助用户快速決策
2.0 版本:
但隨着得物還價功能不斷加強,系統面臨了一些煩惱:
- 場景維度:訂單列表、想要<收藏>列表等新場景接入
- 流量維度:部分頁面的訪問量呈指數級增長,峯值較初期上升明顯
我們發現原有設計逐漸顯現出一些侷限性:
- 用户體驗優化:隨着用户規模快速增長,如何在高併發場景下依然保持絲滑流暢的還價體驗,成為重要關注點
- 迭代效率:每次新增業務場景都需要重複開發相似邏輯
- 協作效率:功能迭代的溝通和對接成本增加
改造點
針對上述問題,我們採用策略模式進行代碼結構升級,核心改造點包括:
抽象策略接口
public interface xxxQueryStrategy {
/**
* 策略類型
*
* @return 策略類型
*/
String matchType();
/**
* 前置校驗
*
* @param ctx xxx上下文
* @return true-校驗通過;false-校驗未通過
*/
boolean beforeProcess(xxxCtx ctx);
/**
* 執行策略
*
* @param ctx xxx上下文
* @return xxxdto
*/
xxxQueryDTO handle(xxxtx ctx);
/**
* 後置處理
*
* @param ctx xxx上下文
*/
void afterProcess(xxxCtx ctx);
}
抽象基類 :封裝公共數據查詢邏輯
@Slf4j
@Component
public abstract class AbstractxxxStrategy {
/**
* 執行策略
*
* @param ctx xxx上下文
*/
public void doHandler(xxxCtx ctx) {
// 初始化xxx數據
initxxx(ctx);
// 異步查詢相關信息
supplyAsync(ctx);
// 初始化xxx上下文
initxxxCtx(ctx);
// 查詢xxxx策略
queryxxxGuide(ctx);
// 查詢xxx底部策略
queryxxxBottomGuide(ctx);
}
/**
* 初始化xxx數據
*
* @param ctx xxx上下文
*/
protected abstract void initxxx(xxxCtx ctx);
/**
* 異步查詢相關信息
*
* @param ctx xxx上下文
*/
protected abstract void supplyAsync(xxxCtx ctx);
/**
* 初始化xxx上下文
*
* @param ctx xxx上下文
*/
protected abstract void initxxxCtx(xxxCtx ctx);
/**
* 查詢xxx策略
*
* @param ctx xxx上下文
*/
protected abstract void queryxxxGuide(xxxCtx ctx);
/**
* 查詢xxx底部策略
*
* @param ctx xxx上下文
*/
protected abstract void queryxxxBottomGuide(xxxCtx ctx);
/**
* 構建出參
*
* @param ctx xxx上下文
*/
protected abstract void buildXXX(xxxCtx ctx);
}
具體策略 :實現場景特有邏輯
public class xxxStrategy extends AbstractxxxxStrategy implements xxxStrategy {
/**
* 策略類型
*
* @return 策略類型
*/
@Override
public String matchType() {
// XXX
}
/**
* 前置校驗
*
* @param ctx xxx上下文
* @return true-校驗通過;false-校驗未通過
*/
@Override
public boolean beforeProcess(xxxCtx ctx) {
// XXX
}
/**
* 執行策略
*
* @param ctx xxx上下文
* @return 公共出參
*/
@Override
public BuyerBiddingQueryDTO handle(xxxCtx ctx) {
super.doHandler(ctx);
// XXX
}
/**
* 後置處理
*
* @param ctx xxx上下文
*/
@Override
public void afterProcess(xxxCtx ctx) {
// XXX
}
/**
* 初始化xxx數據
*
* @param ctx xxx上下文
*/
@Override
protected void initxxx(xxxCtx ctx) {
// XXX
}
/**
* 異步查詢相關信息
*
* @param ctx XXX上下文
*/
@Override
protected void supplyAsync(xxxCtx ctx) {
// 前置異步查詢
super.preBatchAsyncxxx(ctx);
// 策略定製業務
// XXX
}
/**
* 初始化XXX上下文
*
* @param ctx XXX上下文
*/
@Override
protected void initGuideCtx(xxxCtx ctx) {
// XXX
}
/**
* 查詢XXX策略
*
* @param ctx XXX上下文
*/
@Override
protected void queryXXXGuide(xxxCtx ctx) {
// XXX
}
/**
* 查詢XXX策略
*
* @param ctx XXX上下文
*/
@Override
protected void queryXXXBottomGuide(XXXCtx ctx) {
// XXX
}
/**
* 構建出參
*
* @param ctx XXX上下文
*/
@Override
protected void buildXXX(XXXCtx ctx) {
// XXX
}
}
運行時策略路由
@Component
@RequiredArgsConstructor
public class xxxStrategyFactory {
private final List<xxxStrategy> xxxStrategyList;
private final Map<String, xxxStrategy> strategyMap = new HashMap<>();
@PostConstruct
public void init() {
CollectionUtils.emptyIfNull(xxxStrategyList)
.stream()
.filter(Objects::nonNull)
.forEach(strategy -> strategyMap.put(strategy.matchType(), strategy));
}
public xxxStrategy select(String scene) {
return strategyMap.get(scene);
}
}
升級收益
1.性能提升 :
- 同步調用改為CompletableFuture異步編排
- 並行化獨立IO操作,降低整體響應時間
2.擴展性增強 :
- 新增場景只需實現新的Strategy類
- 符合開閉原則(對擴展開放,對修改關閉)
3.可維護性改善 :
- 業務邏輯按場景垂直拆分
- 公共邏輯下沉到抽象基類
- 消除複雜的條件分支判斷
4.架構清晰度 :
- 明確的策略接口定義
- 各策略實現類職責單一
這種架構改造體現了組合優於繼承 、面向接口編程等設計原則,通過策略模式將原本複雜的單體式結構拆分為可插拔的組件,為後續業務迭代提供了良好的擴展基礎。
四、總結
在軟件開發中,設計模式是一種解決特定場景問題的通用方法論,旨在提升代碼的可讀性、可維護性和可複用性。其核心優勢在於清晰的職責分離理念、靈活的行為抽象能力以及對系統結構的優化設計。結合豐富的實踐經驗,設計模式已經成為開發者應對複雜業務需求、構建高質量軟件系統的重要指導原則。
本文通過解析一些經典設計模式的原理、框架應用與實戰案例,深入探討了設計模式在實際開發中的價值與作用。作為代碼優化的工具,更作為一種開發哲學,設計模式以簡潔優雅的方式解決複雜問題,推動系統的高效與穩健。
當然了,在實際的軟件開發中,我們應根據實際需求合理選擇和應用設計模式,避免過度設計,同時深入理解其背後的理念,最終實現更加高效、健壯的代碼與系統架構。
往期回顧
1.從0到1搭建一個智能分析OBS埋點數據的AI Agent|得物技術
2.數據庫AI方向探索-MCP原理解析&DB方向實戰|得物技術
3.項目性能優化實踐:深入FMP算法原理探索|得物技術
4.Dragonboat統一存儲LogDB實現分析|得物技術
5.從數字到版面:得物數據產品裏數字格式化的那些事
文 /忘川
關注得物技術,每週更新技術乾貨
要是覺得文章對你有幫助的話,歡迎評論轉發點贊~
未經得物技術許可嚴禁轉載,否則依法追究法律責任。