什麼是策略模式?

策略模式(Strategy Pattern) 是一種行為設計模式,它定義了一系列算法,並將每個算法封裝起來,使它們可以相互替換。策略模式讓算法的變化獨立於使用算法的客户端。

簡單來説:定義策略家族,讓客户端自由選擇。

現實世界類比

想象你去餐廳吃飯:

  • 策略接口:點菜這個行為
  • 具體策略:中餐、西餐、日料等不同菜系
  • 上下文:餐廳(提供點菜環境)
  • 客户端:你(根據心情選擇今天吃什麼)

模式結構

classDiagram
    class Strategy {
        <>
        +executeStrategy() void
    }
    class ConcreteStrategyA {
        +executeStrategy() void
    }
    class ConcreteStrategyB {
        +executeStrategy() void
    }
    class Context {
        -strategy: Strategy
        +setStrategy(Strategy) void
        +execute() void
    }
    Strategy <|.. ConcreteStrategyA
    Strategy <|.. ConcreteStrategyB
    Context --> Strategy

代碼示例:支付系統設計

問題場景

我們需要實現一個支付系統,支持多種支付方式(支付寶、微信、銀行卡),並且未來可能添加新的支付方式。

傳統實現(不推薦)

kotlin

複製

class PaymentService {
    fun pay(amount: Double, paymentType: String) {
        when (paymentType) {
            "alipay" -> {
                // 複雜的支付寶支付邏輯
                println("支付寶支付:$amount 元")
                // 幾十行代碼...
            }
            "wechat" -> {
                // 複雜的微信支付邏輯
                println("微信支付:$amount 元")
                // 幾十行代碼...
            }
            "bank" -> {
                // 複雜的銀行卡支付邏輯
                println("銀行卡支付:$amount 元")
                // 幾十行代碼...
            }
            else -> throw IllegalArgumentException("不支持的支付方式")
        }
    }
}
// 使用示例
val service = PaymentService()
service.pay(100.0, "alipay") // 添加新支付方式需要修改這個類!

問題分析:

  • ❌ 違反開閉原則:添加新支付方式需要修改現有代碼
  • ❌ 職責過重:一個類包含所有支付邏輯
  • ❌ 難以測試:支付邏輯耦合在一起

策略模式實現(推薦)

1. 定義策略接口

kotlin

複製

interface PaymentStrategy {
    fun pay(amount: Double): Boolean
    fun getStrategyName(): String
}
2. 實現具體策略

kotlin

複製

// 支付寶支付策略
class AlipayStrategy : PaymentStrategy {
    override fun pay(amount: Double): Boolean {
        println("調用支付寶API,支付金額:$amount 元")
        // 實際的支付寶支付邏輯
        return true // 支付成功
    }
    override fun getStrategyName() = "支付寶"
}
// 微信支付策略
class WechatPayStrategy : PaymentStrategy {
    override fun pay(amount: Double): Boolean {
        println("調用微信支付API,支付金額:$amount 元")
        // 實際的微信支付邏輯
        return true
    }
    override fun getStrategyName() = "微信支付"
}
// 銀行卡支付策略
class BankCardStrategy : PaymentStrategy {
    override fun pay(amount: Double): Boolean {
        println("調用銀聯API,支付金額:$amount 元")
        // 實際的銀行卡支付邏輯
        return true
    }
    override fun getStrategyName() = "銀行卡"
}
3. 支付上下文

kotlin

複製

class PaymentContext {
    private var strategy: PaymentStrategy? = null
    fun setPaymentStrategy(strategy: PaymentStrategy) {
        this.strategy = strategy
        println("支付方式切換為:${strategy.getStrategyName()}")
    }
    fun executePayment(amount: Double): Boolean {
        return strategy?.pay(amount) ?: throw IllegalStateException("請先設置支付策略")
    }
    fun getCurrentStrategy() = strategy?.getStrategyName() ?: "未設置"
}
4. 客户端使用

kotlin

複製

fun main() {
    val context = PaymentContext()
    // 用户選擇支付寶支付
    context.setPaymentStrategy(AlipayStrategy())
    context.executePayment(100.0)
    // 用户切換為微信支付
    context.setPaymentStrategy(WechatPayStrategy())
    context.executePayment(200.0)
    // 動態根據條件選擇策略
    val userPreference = getUserPaymentPreference() // 模擬獲取用户偏好
    val strategy = when (userPreference) {
        "alipay" -> AlipayStrategy()
        "wechat" -> WechatPayStrategy()
        "bank" -> BankCardStrategy()
        else -> AlipayStrategy() // 默認策略
    }
    context.setPaymentStrategy(strategy)
    context.executePayment(150.0)
}
fun getUserPaymentPreference() = "wechat" // 模擬數據

更復雜的實戰案例:折扣策略系統

業務需求

電商平台需要支持多種折扣策略:

  • 普通折扣(固定比例)
  • 滿減折扣(滿100減20)
  • 會員折扣(根據會員等級)
  • 季節性折扣(特定時間段)

策略模式實現

kotlin

複製

// 折扣策略接口
interface DiscountStrategy {
    fun calculateDiscount(originalPrice: Double): Double
    fun getStrategyDescription(): String
}
// 具體策略實現
class PercentageDiscountStrategy(private val discountRate: Double) : DiscountStrategy {
    override fun calculateDiscount(originalPrice: Double): Double {
        return originalPrice * discountRate
    }
    override fun getStrategyDescription() = "${(discountRate * 100)}% 折扣"
}
class FullReductionStrategy(private val fullAmount: Double, private val reduction: Double) : DiscountStrategy {
    override fun calculateDiscount(originalPrice: Double): Double {
        return if (originalPrice >= fullAmount) reduction else 0.0
    }
    override fun getStrategyDescription() = "滿 $fullAmount 減 $reduction"
}
class MemberDiscountStrategy(private val memberLevel: String) : DiscountStrategy {
    private val discountRates = mapOf(
        "gold" to 0.8,    // 金卡8折
        "silver" to 0.9,  // 銀卡9折
        "normal" to 0.95  // 普通卡95折
    )
    override fun calculateDiscount(originalPrice: Double): Double {
        val rate = discountRates[memberLevel] ?: 1.0
        return originalPrice * (1 - rate)
    }
    override fun getStrategyDescription() = "${memberLevel}會員折扣"
}
// 折扣上下文
class DiscountContext {
    private var strategy: DiscountStrategy = PercentageDiscountStrategy(1.0) // 默認無折扣
    fun setDiscountStrategy(strategy: DiscountStrategy) {
        this.strategy = strategy
    }
    fun calculateFinalPrice(originalPrice: Double): Double {
        val discount = strategy.calculateDiscount(originalPrice)
        val finalPrice = originalPrice - discount
        println("原價:$originalPrice")
        println("折扣策略:${strategy.getStrategyDescription()}")
        println("折扣金額:$discount")
        println("最終價格:$finalPrice")
        println("---")
        return finalPrice
    }
}
// 使用示例
fun main() {
    val context = DiscountContext()
    val originalPrice = 200.0
    // 測試不同折扣策略
    context.setDiscountStrategy(PercentageDiscountStrategy(0.8)) // 8折
    context.calculateFinalPrice(originalPrice)
    context.setDiscountStrategy(FullReductionStrategy(100.0, 20.0)) // 滿100減20
    context.calculateFinalPrice(originalPrice)
    context.setDiscountStrategy(MemberDiscountStrategy("gold")) // 金卡會員
    context.calculateFinalPrice(originalPrice)
}

策略模式的優點

✅ 開閉原則

添加新策略無需修改現有代碼:

kotlin

複製

// 新增一個節日折扣策略
class FestivalDiscountStrategy : DiscountStrategy {
    override fun calculateDiscount(originalPrice: Double): Double {
        return originalPrice * 0.7 // 節日7折
    }
    override fun getStrategyDescription() = "節日特惠折扣"
}
// 使用新策略(無需修改現有代碼)
context.setDiscountStrategy(FestivalDiscountStrategy())

✅ 消除條件判斷

用多態替代複雜的條件語句,代碼更清晰。

✅ 易於測試

每個策略可以獨立測試:

kotlin

複製

@Test
fun testPercentageDiscount() {
    val strategy = PercentageDiscountStrategy(0.8)
    assertEquals(80.0, strategy.calculateDiscount(100.0))
}

✅ 算法複用

策略可以在不同上下文中複用。

適用場景

推薦使用策略模式的情況:

  1. 多種算法變體:一個功能有多個實現版本
  2. 避免條件爆炸:複雜的if-else或switch-case語句
  3. 算法需要獨立變化:不同算法可能獨立演化和替換
  4. 客户端需要靈活選擇:運行時動態切換算法

不適用的情況:

  1. 算法很少變化:如果算法基本固定,過度設計反而複雜
  2. 客户端直接調用簡單算法:如果算法很簡單,直接調用即可
  3. 算法間差異很小:如果算法基本相同,用參數控制更簡單

與其他模式的關係

策略模式 vs 狀態模式

  • 策略模式:客户端主動選擇算法
  • 狀態模式:狀態自動轉換,客户端不感知狀態變化

策略模式 vs 工廠模式

  • 策略模式:關注算法的選擇和替換
  • 工廠模式:關注對象的創建和初始化
  • 經常結合使用:工廠創建策略對象

Kotlin 的優化實現

利用 Kotlin 的語言特性,可以寫出更簡潔的策略模式:

使用函數類型

kotlin

複製

typealias DiscountStrategy = (Double) -> Double
val percentageDiscount: DiscountStrategy = { price -> price * 0.8 }
val fullReductionDiscount: DiscountStrategy = { price -> if (price > 100) price - 20 else price }
class DiscountCalculator(private var strategy: DiscountStrategy = { it }) {
    fun setStrategy(newStrategy: DiscountStrategy) {
        strategy = newStrategy
    }
    fun calculate(price: Double) = strategy(price)
}
// 使用
val calculator = DiscountCalculator(percentageDiscount)
println(calculator.calculate(100.0)) // 80.0

使用密封類

kotlin

複製

sealed class DiscountStrategy {
    object NoDiscount : DiscountStrategy()
    data class Percentage(val rate: Double) : DiscountStrategy()
    data class FullReduction(val full: Double, val reduction: Double) : DiscountStrategy()
    fun calculate(price: Double): Double = when (this) {
        is NoDiscount -> price
        is Percentage -> price * rate
        is FullReduction -> if (price >= full) price - reduction else price
    }
}

總結

策略模式是應對算法變化的利器,它通過"封裝變化"讓系統更靈活。記住策略模式的核心思想:

找出代碼中可能變化的部分,把它抽離出來,封裝成可互換的策略。

在實際項目中,當您發現複雜的條件判斷或經常需要添加新的算法變體時,就是使用策略模式的最佳時機。