🦔
Rubyで学ぶストラテジーパターン (Strategy Pattern)
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. 技術や手法の"キモ"はどこにある?
-
アルゴリズムのカプセル化
- 処理(アルゴリズム)を独立したクラスとして実装し、他の部分に影響を与えずに変更可能です。
-
依存性注入
- クラスのコンストラクタやメソッドにストラテジーオブジェクトを注入することで、柔軟な切り替えを実現します。
-
動的な切り替え
- 実行時に異なるストラテジーを渡すことで、処理を簡単に変更できます。
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. まとめ
ストラテジーパターンは、アルゴリズムを独立したクラスとして実装し、動的に処理を切り替えるデザインパターンです。
このパターンを適切に活用することで、コードの拡張性と保守性を大幅に向上させることができますが、管理コストを抑えるために適用範囲を慎重に検討することが重要です。
Discussion