🦔

Rubyで学ぶストラテジーパターン (Strategy Pattern)

2025/01/22に公開

1. どんなもの?

ストラテジーパターンは、アルゴリズムを一つのクラスとしてカプセル化し、それらを切り替えることで動的に処理を変更できるデザインパターンです。
このパターンを使うことで、クラスの振る舞いを変更する際に条件分岐を使わず、柔軟に処理を切り替えることができます。

例えば、支払い方法(クレジットカード、銀行振込、PayPal)や検索アルゴリズムを切り替えるような場面で利用されます。

2. 通常の実装方法と比べてどこがすごいの?

通常の方法

処理を変更するために条件分岐を使う場合、コードが複雑になりやすく、拡張性に乏しくなります。

class PaymentProcessor
  def initialize(payment_method)
    @payment_method = payment_method
  end

  def pay(amount)
    if @payment_method == :credit_card
      puts "Paid #{amount} using credit card."
    elsif @payment_method == :bank_transfer
      puts "Paid #{amount} using bank transfer."
    else
      puts "Unknown payment method."
    end
  end
end

processor = PaymentProcessor.new(:credit_card)
processor.pay(100)
  • 課題:
    • 支払い方法が増えるたびに条件分岐が増加し、コードの可読性が低下。
    • 新しい処理を追加する場合、既存コードを変更する必要があり、バグが入りやすい。

ストラテジーパターンの利点

ストラテジーパターンを使うと、処理をクラスとして分離し、動的に切り替えられるようになります。

class CreditCardPayment
  def pay(amount)
    puts "Paid #{amount} using credit card."
  end
end

class BankTransferPayment
  def pay(amount)
    puts "Paid #{amount} using bank transfer."
  end
end

class PaymentProcessor
  def initialize(strategy)
    @strategy = strategy
  end

  def pay(amount)
    @strategy.pay(amount)
  end
end

processor = PaymentProcessor.new(CreditCardPayment.new)
processor.pay(100)
  • 利点:
    • アルゴリズムを個別のクラスとして分離できるため、コードの拡張性が向上。
    • 条件分岐を使わずに処理を切り替えられる。

3. 技術や手法の"キモ"はどこにある?

  1. アルゴリズムのカプセル化

    • 処理(アルゴリズム)を独立したクラスとして実装し、他の部分に影響を与えずに変更可能です。
  2. 依存性注入

    • クラスのコンストラクタやメソッドにストラテジーオブジェクトを注入することで、柔軟な切り替えを実現します。
  3. 動的な切り替え

    • 実行時に異なるストラテジーを渡すことで、処理を簡単に変更できます。

4. 実装例

例1: 検索アルゴリズムの切り替え

異なる検索アルゴリズムを切り替える場面でストラテジーパターンを利用します。

class SimpleSearch
  def search(data, query)
    data.select { |item| item.include?(query) }
  end
end

class AdvancedSearch
  def search(data, query)
    data.select { |item| item.downcase.include?(query.downcase) }
  end
end

class SearchContext
  def initialize(strategy)
    @strategy = strategy
  end

  def search(data, query)
    @strategy.search(data, query)
  end
end

data = ["Ruby", "Rails", "JavaScript"]
context = SearchContext.new(SimpleSearch.new)
puts context.search(data, "Ruby") # => ["Ruby"]
  • 検証ポイント:
    • 検索アルゴリズムを動的に切り替えられる。

例2: 画像処理フィルタの適用

画像処理で異なるフィルタを動的に適用する例です。

class GrayscaleFilter
  def apply(image)
    puts "Applying grayscale filter to #{image}"
  end
end

class SepiaFilter
  def apply(image)
    puts "Applying sepia filter to #{image}"
  end
end

class ImageProcessor
  def initialize(filter)
    @filter = filter
  end

  def process(image)
    @filter.apply(image)
  end
end

processor = ImageProcessor.new(GrayscaleFilter.new)
processor.process("photo.jpg")
  • 検証ポイント:
    • フィルタをストラテジーとして実装し、動的に適用可能。

5. 議論はあるか?

メリット

  • アルゴリズムを個別のクラスとして分離することで、コードの拡張性が向上。
  • 条件分岐を使わずに動的な処理の切り替えが可能。
  • オープン/クローズド原則(OCP: Open/Closed Principle)を満たす。

デメリット

  • ストラテジークラスが増えると管理が煩雑になる。
  • シンプルな処理では、オーバーヘッドが発生する可能性がある。

議論

ストラテジーパターンは柔軟性を提供しますが、適用する場面を見極めることが重要です。
特に、アルゴリズムが頻繁に変わる場面や、条件分岐が複雑な場合に適用すると効果的です。

6. まとめ

ストラテジーパターンは、アルゴリズムを独立したクラスとして実装し、動的に処理を切り替えるデザインパターンです。

このパターンを適切に活用することで、コードの拡張性と保守性を大幅に向上させることができますが、管理コストを抑えるために適用範囲を慎重に検討することが重要です。

GitHubで編集を提案

Discussion