策略模式定義一系列算法,封裝每一個,使它們可互換。配合簡單工廠模式,可根據運行時條件動態選擇具體策略。本文以「圖片保存格式選擇」為例,演示策略接口定義、多種實現、工廠選擇、客戶端調用的完整流程。
| 角色 | 類名 | 職責 |
|---|---|---|
| Strategy 接口 | ImageSaver | 定義統一的 save() 契約 |
| ConcreteStrategy | GifSaver, JpegSaver, PngSaver | 實現具體保存邏輯 |
| Factory | SaverFactory | 根據類型字符串創建策略實例 |
| Context/Client | ImageProcessor | 持有策略引用,委託執行 |
package com.example.pattern;
// ========== 1. 策略接口 ==========
public interface ImageSaver {
/**
* 保存圖片
* @param imageData 圖片二進制數據
* @param destPath 目標文件路徑
*/
void save(byte[] imageData, String destPath) throws IOException;
}
// ========== 2. 具體策略實現 ==========
/** GIF 格式保存 */
public class GifSaver implements ImageSaver {
@Override
public void save(byte[] imageData, String destPath) throws IOException {
// 實際項目中使用 ImageIO 或第三方庫
Files.write(Paths.get(destPath), imageData);
System.out.println("[GIF] 圖片已保存至:" + destPath);
}
}
/** JPEG 格式保存(支持質量參數) */
public class JpegSaver implements ImageSaver {
private final float quality; // 0.0f - 1.0f
public JpegSaver(float quality) {
this.quality = Math.max(0.1f, Math.min(1.0f, quality));
}
@Override
public void save(byte[] imageData, String destPath) throws IOException {
// 這裡演示邏輯,實際需用 ImageWriter 設置壓縮質量
Files.write(Paths.get(destPath), imageData);
System.out.printf("[JPEG] 質量 %.0f%% 保存至:%s%n", quality * 100, destPath);
}
}
/** PNG 格式保存(無損壓縮) */
public class PngSaver implements ImageSaver {
@Override
public void save(byte[] imageData, String destPath) throws IOException {
Files.write(Paths.get(destPath), imageData);
System.out.println("[PNG] 無損保存至:" + destPath);
}
}
// ========== 3. 策略工廠(簡單工廠 + 註冊表模式) ==========
public class SaverFactory {
// 策略註冊表:類型 -> 實例供應商(支持延遲創建、參數化)
private static final Map> REGISTRY = new ConcurrentHashMap<>();
static {
// 默認註冊
register("gif", GifSaver::new);
register("jpeg", () -> new JpegSaver(0.9f)); // 默認 90% 質量
register("jpg", () -> new JpegSaver(0.9f));
register("png", PngSaver::new);
register("webp", () -> new WebpSaver(0.8f)); // 擴展示例
}
/** 註冊自定義策略(開放擴展) */
public static void register(String type, Supplier supplier) {
REGISTRY.put(type.toLowerCase(), supplier);
}
/** 獲取策略實例 */
public static ImageSaver getSaver(String type) {
Supplier supplier = REGISTRY.get(type.toLowerCase());
if (supplier == null) {
throw new IllegalArgumentException("不支持的圖片格式:" + type +
",支持列表:" + REGISTRY.keySet());
}
return supplier.get(); // 每次獲取新實例(無狀態可共享單例)
}
/** 獲取所有支持格式 */
public static Set getSupportedFormats() {
return Collections.unmodifiableSet(REGISTRY.keySet());
}
}
// ========== 4. 新增格式示例:WebP ==========
class WebpSaver implements ImageSaver {
private final float quality;
WebpSaver(float q) { this.quality = q; }
@Override public void save(byte[] data, String path) throws IOException {
Files.write(Paths.get(path), data);
System.out.printf("[WebP] 質量 %.0f%% 保存至:%s%n", quality * 100, path);
}
}
// ========== 5. 上下文/客戶端使用 ==========
public class ImageProcessor {
private ImageSaver currentSaver;
public void setFormat(String format) {
this.currentSaver = SaverFactory.getSaver(format);
}
public void saveImage(byte[] imageData, String outputPath) throws IOException {
if (currentSaver == null) {
throw new IllegalStateException("未設置保存格式,請先調用 setFormat()");
}
currentSaver.save(imageData, outputPath);
}
// ========== 測試主方法 ==========
public static void main(String[] args) throws IOException {
ImageProcessor processor = new ImageProcessor();
byte[] dummyData = "模擬圖片數據".getBytes();
// 用戶選擇 GIF
processor.setFormat("gif");
processor.saveImage(dummyData, "output/image.gif");
// 用戶選擇 JPEG(高質量)
processor.setFormat("jpeg");
processor.saveImage(dummyData, "output/photo.jpg");
// 用戶選擇 PNG
processor.setFormat("png");
processor.saveImage(dummyData, "output/screenshot.png");
// 動態註冊新格式(插件化擴展)
SaverFactory.register("avif", () -> new AvifSaver());
processor.setFormat("avif");
processor.saveImage(dummyData, "output/nextgen.avif");
System.out.println("\n支持格式:" + SaverFactory.getSupportedFormats());
}
}
// ========== 6. 假設的 AVIF 策略(展示擴展性) ==========
class AvifSaver implements ImageSaver {
@Override public void save(byte[] data, String path) throws IOException {
Files.write(Paths.get(path), data);
System.out.println("[AVIF] 新一代格式保存至:" + path);
}
}
[GIF] 圖片已保存至:output/image.gif
[JPEG] 質量 90% 保存至:output/photo.jpg
[PNG] 無損保存至:output/screenshot.png
[AVIF] 新一代格式保存至:output/nextgen.avif
支持格式:[gif, jpeg, jpg, png, webp, avif]
ImageSaver 並註冊,無需修改現有代碼Supplier 延遲創建,支持參數化構造(如 JPEG 質量)// 根據用戶選擇、文件擴展名、配置文件動態決定
String format = detectFormatFromUserSelection(); // 或 fileExt, 或 config
processor.setFormat(format);
processor.saveImage(data, path);
ImageSaver 測試 Processor 邏輯// 支持運行時參數
public class ParametricJpegSaver implements ImageSaver {
private final float quality;
private final boolean progressive;
public ParametricJpegSaver(float quality, boolean progressive) { ... }
// 工廠註冊時綁定參數
SaverFactory.register("jpeg-high", () -> new ParametricJpegSaver(0.95f, true));
SaverFactory.register("jpeg-web", () -> new ParametricJpegSaver(0.75f, false));
@Component
public class SpringSaverFactory {
private final Map savers = new HashMap<>();
@Autowired
public SpringSaverFactory(List savers) {
savers.forEach(s -> this.savers.put(s.getFormat(), s));
}
// ...
}
@Component("gifSaver") @Qualifier("gif") class GifSaverImpl implements ImageSaver { ... }
// 嘗試 WebP → 不支持則回退 JPEG → 最後 PNG
public class FallbackSaver implements ImageSaver {
private final List chain = List.of(new WebpSaver(), new JpegSaver(0.8f), new PngSaver());
// save() 依次嘗試...
}
策略模式 + 簡單工廠(註冊表變體)實現了「算法族內部封裝、外部動態選擇、無限擴展不修改核心」。關鍵點:接口定義契約、實現隔離變化、工廠解耦創建、註冊表支持開放擴展。這是框架設計、插件系統、多格式處理的經典最佳實踐。