策略模式和模板方法模式的區別【面試題】
摘要:
策略模式和模板方法模式均屬于行為設計模式,但核心差異顯著。策略模式通過組合實現,支持運行時動態切換完整算法(如支付方式切換),變化維度大;模板方法模式通過繼承實現,固定算法骨架但允許子類定制特定步驟(如文檔生成流程不變,數據源可變),變化維度小。前者由調用方主動選擇策略(控制權在客戶端),后者遵循“好萊塢原則”由父類控制流程調用子類方法(控制權在父類)。選擇依據:需靈活替換整套邏輯用策略模式;需復用流程但微調步驟用模板方法模式。
簡單記憶:
第一,機制不同。模板方法靠繼承,策略靠組合。這意味著模板方法在編譯時就確定了行為擴展(通過子類化)相對變化小,而策略可以在運行時切換,變化的大。
第二,變化維度不同。模板方法變化的是算法中的某些步驟,整體流程不變;策略變化的是整個算法實現。就像做菜,模板方法是固定了“洗菜-切菜-烹飪”流程但允許換刀工,策略則是直接換菜系。
第三,控制方向相反。模板方法由父類控制流程調用子類方法(好萊塢原則),策略是調用方主動選擇策略對象。
策略模式(Strategy Pattern)和模板方法模式(Template Method Pattern)都是行為設計模式,都用于封裝變化的部分,但它們解決問題的角度、實現機制和適用場景有本質區別。以下是兩者核心區別的清晰對比:
1)相同點
策略模式(Strategy Pattern)和模板方法模式(Template Method Pattern)都是行為設計模式,都用于封裝變化的部分,但它們解決問題的角度、實現機制和適用場景有本質區別。以下是兩者核心區別的清晰對比:
2)不同點
2.1)核心目標不同
- 策略模式:
替換整個算法。
目標是定義一系列可互換的算法,讓客戶端能根據需要動態切換不同的算法實現,且算法的變化不影響使用它的上下文。
👉 核心:算法的完整替換。 - 模板方法模式:
定義算法的骨架,允許子類重寫特定步驟。
目標是固定一個操作的整體流程結構,僅允許子類修改流程中的某些步驟(變化點),而不改變算法的主干。
👉 核心:算法骨架不變,步驟細節可變。
2.2)實現機制不同
特性 | 策略模式 | 模板方法模式 |
---|---|---|
關系類型 | 組合(Has-a) | 繼承(Is-a) |
關鍵參與者 | Context 持有 Strategy 接口的引用 | 抽象類定義 模板方法 + 抽象步驟方法 |
擴展方式 | 新增 Strategy 實現類 | 創建子類并實現抽象方法/覆蓋鉤子方法 |
運行時行為 | 可動態切換策略對象 | 子類行為在編譯時確定(通過繼承) |
控制流方向 | Context 委托給策略對象執行 | 父類調用子類方法(“好萊塢原則”) |
2.3)變化點不同
- 策略模式:
變化的是整個算法邏輯。
例如:支付時在支付寶
、信用卡
、PayPal
等完全不同的支付邏輯之間切換。 - 模板方法模式:
變化的是算法中的某些步驟,整體流程固定不變。
例如:文檔生成流程打開模板->填充數據->保存文件
中,填充數據
的方式可變(填數據庫數據/API數據),但步驟順序不變。
2.4)代碼結構對比
策略模式偽代碼
// 策略接口
interface CompressionStrategy {void compress(File file);
}// 具體策略
class ZipCompression implements CompressionStrategy {void compress(File file) { /* ZIP壓縮邏輯 */ }
}class RarCompression implements CompressionStrategy {void compress(File file) { /* RAR壓縮邏輯 */ }
}// 上下文(組合策略)
class Compressor {private CompressionStrategy strategy;void setStrategy(CompressionStrategy s) { this.strategy = s; }void compressFile(File file) {strategy.compress(file); // 委托給當前策略}
}// 使用:動態切換策略
Compressor compressor = new Compressor();
compressor.setStrategy(new ZipCompression()); // 運行時切換為ZIP
compressor.compressFile(file);
模板方法模式偽代碼
// 抽象類(定義模板骨架)
abstract class DataExporter {// 模板方法 (final 防止子類覆蓋流程)public final void export() {openConnection();fetchData(); // 抽象方法 -> 子類實現transformData(); // 鉤子方法 -> 子類可選覆蓋save(); // 抽象方法 -> 子類實現closeConnection();}protected abstract void fetchData();protected abstract void save();protected void transformData() {} // 默認空實現(鉤子方法)private void openConnection() { /* 通用邏輯 */ }private void closeConnection() { /* 通用邏輯 */ }
}// 具體子類
class CSVExporter extends DataExporter {protected void fetchData() { /* 從DB取數據 */ }protected void save() { /* 存為CSV */ }
}class JSONExporter extends DataExporter {protected void fetchData() { /* 從API取數據 */ }protected void save() { /* 存為JSON */ }protected void transformData() { /* 自定義數據轉換 */ } // 覆蓋鉤子
}// 使用:子類繼承固定流程
DataExporter exporter = new CSVExporter();
exporter.export(); // 執行固定流程+子類實現的步驟
2.5) 適用場景對比
場景 | 策略模式 | 模板方法模式 |
---|---|---|
算法需要動態切換 | ? 支付方式、排序算法、導航策略等 | ? 子類行為在編譯時綁定 |
多個類有相同流程但不同步驟 | ? | ? 框架生命周期、文檔生成、ETL流程 |
避免重復的流程代碼 | ? | ? 將通用流程抽取到父類 |
隱藏算法實現細節 | ? 客戶端只依賴策略接口 | ? 子類需知曉抽象步驟 |
擴展新算法/行為 | ? 新增策略類即可 | ? 新增子類實現抽象步驟 |
控制子類擴展點 | ? | ? 用 final 模板方法保護核心流程 |
關鍵總結:一句話區分
- 策略模式:“換整套方案”
→ 通過組合動態切換完整算法(如支付時換整套支付邏輯)。 - 模板方法模式:“固定流程,定制步驟”
→ 通過繼承固定算法骨架,子類定制某些步驟(如報告生成流程固定,但數據來源和格式可定制)。
💡 簡單記憶:
- 需要運行時靈活替換整個算法?→ 策略模式(組合)。
- 需要復用固定流程但允許步驟自定義?→ 模板方法模式(繼承)。
喜歡我的文章記得點個在看,或者點贊,持續更新中ing…