策略模式 Strategy Pattern

設計模式(design pattern)主要分爲如下三種類型:git

  1. 結構型模式(structural design pattern):描述對象的構成,以及如何組合對象以造成更大的結構,也就是了解元件間關係,以簡化設計。Structural design patter 包含 Model-View-Controller (MVC)、Model-View-ViewModel (MVVM) 和外觀模式 (Facade)。github

  2. 行爲型模式(behavioral design pattern):描述對象間如何通訊,以便在進行這些交流活動時加強彈性。Behavioral design pattern 包含 Delegation、Strategy、Observer。編程

  3. 建立型模式(creational design pattern):處理對象如何建立,根據實際狀況使用合適的方式建立對象。基本的對象建立方式可能會致使設計上的問題,或增長設計的複雜度,creational design pattern 經過以某種方式控制對象的建立來解決問題。Creational design pattern 包含 Builder、Singleton 和 Prototype。swift

1.什麼是策略模式

策略模式 strategy pattern 屬於 behavioral pattern。Strategy pattern 定義了一系列可互換替代的對象,能夠在 runtime 時設置或切換。策略模式包含如下三部分:設計模式

StrategyPatternUML

  • 使用策略模式的對象:一般爲視圖控制器,也能夠是任何有互換替代 (interchangeable) 需求的對象。bash

  • Strategy protocol:每種策略都必須遵照的協議。app

  • Strategies:遵照 strategy protocol 協議的對象,相互間可互換代替。ide

2. 什麼時候使用策略模式

當有兩種或更多可相互代替的行爲時,使用策略模式。函數

Strategy pattern 和 Delegation pattern 很像。均依賴 protocol 而非具體對象以增長靈活性。所以,任何實現了 strategy protocol 的對象在運行時都可做爲一種策略。與委託不一樣的是,strategy pattern 使用一族對象。單元測試

Delegate 在運行時固定不變。例如,UITableView的dataSource和delegate設置後不須要改變。Strategy 在運行時能夠相互切換。

3. Demo

Demo 是購物車結算模塊。結算方式有正常價格、九折和免運費三種。

3.1 不使用 strategy pattern

更新ShoppingCartViewController.swift文件中prepare(for:sender:)方法:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    view.endEditing(false)
    
        if let destinationViewController: DetailViewController = segue.destination as? DetailViewController {
            destinationViewController.bindProperties(itemPrices:   itemPrices, checkoutType: .normal)
        }
    }
複製代碼

在TotalPriceViewController.swift文件添加如下方法:

public func bindProperties(itemPrices: [Int], checkoutType: CheckoutType) {
        switch checkoutType {
        case .normal:
            finalPrice = getFinalPriceWithNormal(itemPrices: itemPrices)
            
        case .discount:
            finalPrice = getFinalPriceWithDiscount(itemPrices: itemPrices)
            
        case .freeShipping:
            finalPrice = getFinalPriceWithFreeShipping(itemPrices: itemPrices)
        }
    }
    
    private func getFinalPriceWithNormal(itemPrices: [Int]) -> Int {
        // do calculation
        
        return 90 + 75 + 20
    }
    
    private func getFinalPriceWithDiscount(itemPrices: [Int]) -> Int {
        // do calculation
        
        return Int((90 + 75) * 0.9) + 20
    }
    
    private func getFinalPriceWithFreeShipping(itemPrices: [Int]) -> Int {
        // do calculation
        
        return 90 + 75
    }
複製代碼

上述代碼能夠根據不一樣枚舉類型計算總價,那爲何要用 strategy pattern ?

Strategy pattern 可讓代碼遵照開閉原則 (Open/Closed principle)。在面向對象編程領域中,開閉原則規定「軟件中的對象、類、模塊和函數等,對於擴展應該是開放的,但對於修改是封閉的」。這意味着,一個對象容許在不改變其源代碼的前提下變動它的行爲。該特性在產品化的環境中是特別有價值的。在這種環境中,改變源代碼須要代碼審查、單元測試等以確保產品質量。遵循這種原則的代碼在擴展時並不發生變化,所以,無需上述過程。

目前,在TotalPriceViewController.swift文件添加了enum和所需方法,若是增長告終算方式,就須要更改TotalPriceViewController.swift文件中代碼。但在TotalPriceViewController.swift文件中增長enum類型和switch,就違背了 Open/Closed principle (open for extension and close for modification) 。同時TotalPriceViewController掌握了太多其沒必要知道的內容,會使代碼高度耦合、難以測試。

3.2 使用 strategy pattern

3.2.1 建立 protocol

添加CheckoutStrategy.swift文件,定義一項協議。在咱們的示例中,該協議只有一個方法。

protocol CheckoutStrategy: class {
    func getFinalPrice(with itemPrices:[Int]) -> Int
}
複製代碼

3.2.2 建立 strategies

建立三種策略,分別執行正常價格、九折和免運費結算模式:

class NormalPriceStrategy: CheckoutStrategy {
    func getFinalPrice(with itemPrices: [Int]) -> Int {
        // do calculation
        
        return 90 + 75 + 20
    }
}

class DiscountStrategy: CheckoutStrategy {
    func getFinalPrice(with itemPrices: [Int]) -> Int {
        // do calculation
        
        return Int((90 + 75) * 0.9) + 20
    }
}

class FreeShippingStrategy: CheckoutStrategy {
    func getFinalPrice(with itemPrices: [Int]) -> Int {
        // do calculation
        
        return 90 + 75
    }
}
複製代碼

能夠看到,每一個類均遵照CheckoutStrategy協議,但協議中方法實現方式有所不一樣,每一個類表明一種策略。

3.2.3 使用策略模式的對象

更新ShoppingCartViewController.swift文件中prepare(for:sender:)方法,使用 strategy pattern 結算:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        view.endEditing(false)
        
        if let destinationViewController: TotalPriceViewController = segue.destination as? TotalPriceViewController {
            destinationViewController.bindProperties(itemPrices: itemPrices, checkoutStrategy: NormalPriceStrategy())
//            destinationViewController.bindProperties(itemPrices: itemPrices, checkoutStrategy: DiscountStrategy())
//            destinationViewController.bindProperties(itemPrices: itemPrices, checkoutStrategy: FreeShippingStrategy())
        }
    }
複製代碼

同時在TotalPriceViewController.swift文件添加如下方法:

public func bindProperties(itemPrices:[Int], checkoutStrategy: CheckoutStrategy) {
        finalPrice = checkoutStrategy.getFinalPrice(with: itemPrices)
    }
複製代碼

最後,project navigator 目錄以下:

StrategyPatternProjectNavigator.png
如今,添加、修改結算模式時,只須要增長遵照 CheckoutStrategy協議的策略,修改結算策略便可。

4. 更多示例

下面示例也適合使用 strategy pattern 。

4.1 選擇行程

旅行 app 提供單程、往返兩種購票模式。當進入日曆時,根據單程、往返來決定採起哪一種選擇日期策略。例如,若是是往返,則返回日期必須晚於出發日期。

StrategyPatternCalendar.png
所以,咱們有兩種選擇策略,當打開日曆時指定所採用的策略:

protocol CalendarSelectionStrategy {
    func calendar(_ calendar: CalendarView, didSelect date: Date)
}

class OneWaySelectionStrategy: CalendarSelectionStrategy {
    func calendar(_ calendar: CalendarView, didSelect date: Date) {
        // One way selection logic
    }
}

class ReturnWaySelectionStrategy: CalendarSelectionStrategy {
    func calendar(_ calendar: CalendarView, didSelect date: Date) {
        // Return selection logic
    }
}

// Use
showCalendar(usingSelectionStrategy: OneWaySelectionStrategy())
複製代碼

4.2 表格驗證

App 內表格內容有多種文本類型,如文字、數字、密碼、手機號碼等,每種類型有不一樣驗證方式。咱們能夠爲每種類型定義一種驗證策略,這樣每一個文本框就能知道如何驗證其內容是否符合要求。

protocol ValidateStrategy {
    func validate() -> Bool
}
複製代碼

4.3 更換武器

在遊戲中,用戶會常常更換武器。武器的觸發操做方式是同樣的,但每種武器都有本身的射擊方式。用戶無需用知道具體射擊細節,武器自己知道便可。

總結

你已經學過了 strategy pattern ,如下是其關鍵點:

  • Strategy pattern 定義了一族可在運行時互換替代的對象。
  • Strategy pattern 包含三部分,使用 strategy 的對象,strategy protocol,一族策略。
  • Strategy pattern 和 delegation pattern 都使用 protocol,但 strategy pattern 意味着在運行時切換,delegate 一般是固定的。

當用不一樣的策略作不一樣的事情時,原來類將變得更簡潔。當用 strategy 擴展類時,無需更新採用 strategy 的類,遵照了 Open/Close principle 。

Demo名稱:StrategyPattern

源碼地址:github.com/pro648/Basi…

轉載於做者:pro648

連接:www.jianshu.com/p/c53f2868e…

相關文章
相關標籤/搜索