Menu
Coddy logo textTech

ストラテジーパターン

CoddyのSwiftジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 45/57。

Strategy パターンでは、アルゴリズムの各バリエーションを、共通のプロトコルの背後にある独自の型に配置します。これを使用するコードは Strategy を保持し、実行時に切り替えられます。

protocol ShippingStrategy {
    func cost(weight: Int) -> Int
}
struct Standard: ShippingStrategy { func cost(weight: Int) -> Int { 5 + weight } }
struct Express: ShippingStrategy { func cost(weight: Int) -> Int { 15 + weight * 2 } }

struct Checkout {
    var shipping: any ShippingStrategy
    func total(price: Int, weight: Int) -> Int { price + shipping.cost(weight: weight) }
}

var c = Checkout(shipping: Standard())
print(c.total(price: 100, weight: 3))
c.shipping = Express()
print(c.total(price: 100, weight: 3))

出力:

108
121

Swiftでは、1つのメソッドを持つ戦略にはクロージャで十分なことがよくあります。型は関数を保持し、それを呼び出します。

struct Sorter {
    var rule: (String, String) -> Bool
    func sort(_ words: [String]) -> [String] { words.sorted(by: rule) }
}

let words = ["banana", "kiwi", "apple"]
var s = Sorter(rule: <)
print(s.sort(words))
s.rule = { $0.count < $1.count }
print(s.sort(words))

出力:

["apple", "banana", "kiwi"]
["kiwi", "apple", "banana"]

この戦略は、アルゴリズム内の switch を置き換えます。新しいバリアントは新しい型(またはクロージャ)となり、それらを使用するコードは変更されません:

// ストラテジーなし: 新しいオプションごとにこの関数を編集する
func cost(kind: String, weight: Int) -> Int {
    switch kind { case "standard": return 5 + weight; default: return 15 + weight * 2 }
}
// ストラテジーあり: Checkout は決して変更されない
struct Overnight: ShippingStrategy { func cost(weight: Int) -> Int { 40 } }

戦略は、たとえばユーザー設定などのデータから、小さなファクトリを使って選択できます。

protocol Discount { func apply(_ price: Int) -> Int }
struct NoDiscount: Discount { func apply(_ p: Int) -> Int { p } }
struct Percent: Discount { let rate: Int; func apply(_ p: Int) -> Int { p - p * rate / 100 } }

func discount(for member: String) -> any Discount {
    member == "gold" ? Percent(rate: 20) : NoDiscount()
}

print(discount(for: "gold").apply(200))
print(discount(for: "basic").apply(200))

出力:

160
200
challenge icon

チャレンジ

簡単

ゲームキャラクターの攻撃は戦略です。プロトコル Attack には、name と damage(strength:) -> Int があります。Sword(strength * 2)、Bow(strength + 5)、Magic(保存された mana を持ち、mana が少なくとも 10 の場合はダメージが strength * 3、それ以外の場合は 1。damage 自体は mana を変更しない)を実装してください。Hero は自身の strength と attack を保持し、strike() は Ada hits with sword for 20 を返します。

提供されたコードはヒーローの名前と strength を読み取り、その後に equip sword、equip bow、equip magic 15、または strike のコマンドを受け取ります。各 strike の結果を出力します。

コードは Attack.swift、Sword.swift、Bow.swift、Magic.swift、Hero.swift に記述してください。main.swift には提供された入力/出力コードが含まれており、編集できません。

自分で試してみよう

// 提供された入力/出力コード: そのままにしておく
var input: [String] = []
while let line = readLine() { input.append(line) }
let head = input[0].split(separator: ",").map(String.init)
var hero = Hero(name: head[0], strength: Int(head[1])!, attack: Sword())
for cmd in input.dropFirst() {
    let p = cmd.split(separator: " ").map(String.init)
    if p[0] == "strike" { print(hero.strike()); continue }
    switch p[1] {
    case "bow": hero.attack = Bow()
    case "magic": hero.attack = Magic(mana: Int(p[2])!)
    default: hero.attack = Sword()
    }
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Swiftプレイグラウンド