🔥
Rubyで学ぶアダプタパターン (Adapter Pattern)
1. どんなもの?
アダプタパターンは、異なるインターフェースを持つクラス同士を接続するためのデザインパターンです。
本来互換性のないクラスを「アダプタ」と呼ばれる仲介役のクラスを通して連携させることができます。
例えば、外部ライブラリや古いコードベースを新しいインターフェースに合わせて利用する場合に有効です。
2. 通常の実装方法と比べてどこがすごいの?
通常の方法
通常、異なるインターフェースを持つクラスを直接利用する場合、クライアントコードがそのクラスの詳細な実装に依存します。
class LegacySystem
def legacy_method
puts "Performing legacy system action"
end
end
class ModernSystem
def modern_method
puts "Performing modern system action"
end
end
# クライアントコード
legacy_system = LegacySystem.new
modern_system = ModernSystem.new
if some_condition
legacy_system.legacy_method
else
modern_system.modern_method
end
-
課題:
- クライアントコードがlegacy_methodやmodern_methodといった異なるメソッド名を直接呼び出しているため、コードが複雑化。
- 新しいシステムが追加されるたびに、条件分岐を追加する必要があり、保守が困難になる。
アダプタパターンの利点
アダプタを利用すると、異なるインターフェースを統一して扱えるようになります。
# 旧システム
class LegacySystem
def legacy_method
puts "Legacy system functionality"
end
end
# 新システム
class ModernSystem
def modern_method
puts "Modern system functionality"
end
end
# 旧システムと新システムでインターフェースが異なるので、アダプタを利用して統一する
# LegacySystem専用アダプタ
class LegacyAdapter
def initialize(legacy_system)
@legacy_system = legacy_system
end
# 統一されたインターフェース
def unified_method
@legacy_system.legacy_method
end
end
# ModernSystem専用アダプタ
class ModernAdapter
def initialize(modern_system)
@modern_system = modern_system
end
# 統一されたインターフェース
def unified_method
@modern_system.modern_method
end
end
# クライアントコード
legacy_adapter = LegacyAdapter.new(LegacySystem.new)
modern_adapter = ModernAdapter.new(ModernSystem.new)
# 統一されたインターフェースで呼び出し
legacy_adapter.unified_method # => "Legacy system functionality"
modern_adapter.unified_method # => "Modern system functionality"
-
利点:
- クライアントコードは統一されたインターフェースを利用でき、各システムの詳細な仕様を意識する必要がない。
- 新しいシステムが追加されても、アダプタを拡張するだけでクライアントコードには変更が不要。
- コードの一貫性が向上し、可読性が高まる。
3. 技術や手法の"キモ"はどこにある?
-
アダプタの役割
- アダプタは、既存のクラス(既存の機能)とクライアントコードの間に立つ中間的な存在です。
- インターフェースを変換し、互換性を提供します。
-
既存コードの再利用
- アダプタを利用することで、古いコードや外部ライブラリをそのままの形で新しいシステムに組み込むことが可能です。
-
クライアントコードの独立性
- クライアントコードはアダプタのインターフェースにのみ依存するため、変更が容易になります。
4. 実装例
例1: 外部APIとの連携
複数の外部の支払いAPIを利用する際、異なるAPI仕様を統一するためにアダプタを利用します。
# APIの実装
class PayPalAPI
# API ごとに異なるインターフェースを持つ
def make_payment(amount)
puts "PayPal processed payment of $#{amount}"
end
end
class StripeAPI
# API ごとに異なるインターフェースを持つ
def charge(amount_in_cents)
puts "Stripe processed payment of $#{amount_in_cents / 100.0}"
end
end
# 統一されたインターフェースを定義
class PaymentGateway
def process_payment(amount)
raise NotImplementedError, "Subclasses must implement the process_payment method"
end
end
# PayPal用アダプタ
class PayPalAdapter < PaymentGateway
def initialize(paypal_api)
@paypal_api = paypal_api
end
# このメソッドでインターフェースを統一
def process_payment(amount)
@paypal_api.make_payment(amount)
end
end
# Stripe用アダプタ
class StripeAdapter < PaymentGateway
def initialize(stripe_api)
@stripe_api = stripe_api
end
# このメソッドでインターフェースを統一
def process_payment(amount)
@stripe_api.charge(amount * 100) # Stripeは金額をセント単位で扱う
end
end
# クライアントコード
paypal_api = PayPalAPI.new
stripe_api = StripeAPI.new
paypal_adapter = PayPalAdapter.new(paypal_api)
stripe_adapter = StripeAdapter.new(stripe_api)
paypal_adapter.process_payment(100) # => PayPal processed payment of $100
stripe_adapter.process_payment(100) # => Stripe processed payment of $100
-
実装ポイント:
- 外部APIの異なるインターフェースをアダプタで統一することで、クライアントコードがシンプルに。
例2: Adapterの統一(応用編)
例1の外部APIのアダプタをまとめる。
# PayPalAPI のインターフェース
class PayPalAPI
def make_payment(amount)
puts "PayPal processed payment of $#{amount}"
end
end
# StripeAPI のインターフェース
class StripeAPI
def charge(amount_in_cents)
puts "Stripe processed payment of $#{amount_in_cents / 100.0}"
end
end
# アダプタによって統一されたインターフェース
class UnifiedPaymentAdapter
def initialize(api)
@api = api
end
def process_payment(amount)
case @api
when PayPalAPI
@api.make_payment(amount)
when StripeAPI
@api.charge(amount * 100)
else
raise "Unsupported API"
end
end
end
# クライアントコード
paypal_gateway = UnifiedPaymentAdapter.new(PayPalAPI.new)
stripe_gateway = UnifiedPaymentAdapter.new(StripeAPI.new)
paypal_gateway.process_payment(100) # => PayPal processed payment of $100
stripe_gateway.process_payment(100) # => Stripe processed payment of $100
-
実装ポイント:
- Adapterの処理が一箇所にまとまり、新しいAPIを追加する場合は、そのAPIに対応した処理をprocess_payment内に追加すればよい。。
5. 議論はあるか?
メリット
- 異なるインターフェースを統一できるため、システムの一貫性が向上。
- 既存コードや外部ライブラリを変更せずに再利用できる。
- クライアントコードがシンプルで保守性が高くなる。
デメリット
- アダプタを導入することでコードが増え、設計が複雑化する可能性がある。
- アダプタの数が増える場合、管理が煩雑になる。
- 異なるインターフェースを持つクラスが増えるごとに、それぞれに対応するアダプタを作成する必要があり、どのアダプタがどの対象に対応しているのかを把握する負担が増える。
議論
アダプタパターンは、異なるインターフェースの統一や既存コードの再利用が求められる場面で非常に有効です。
ただし、適用箇所が多すぎるとコードベースが複雑になるため、適切な範囲で利用することが重要です。
6. まとめ
アダプタパターンは、異なるインターフェースを持つクラスを統一するための重要なデザインパターンです。
このパターンを活用することで、既存コードや外部サービスを変更することなく、柔軟で拡張性の高いシステムを構築できます。
Discussion