👋
Rubyで学ぶデコレータパターン (Decorator Pattern)
1. どんなもの?
デコレータパターンは、既存のオブジェクトに新しい機能を動的に追加するデザインパターンです。
オブジェクトの構造を変更することなく、柔軟に振る舞いを拡張できます。
2. 通常の実装方法と比べてどこがすごいの?
通常の方法
新しい振る舞いを持つオブジェクトを作成するには、通常サブクラスを作成します。
class BaseComponent
def operation
"Base"
end
end
class ExtendedComponent < BaseComponent
def operation
"#{super} + Extended"
end
end
component = ExtendedComponent.new
puts component.operation # "Base + Extended"
- サブクラスが増えると管理が煩雑になる。
- オブジェクトに新しい振る舞いを追加する際、サブクラスを作る方法は一般的ですが、複数の振る舞いを組み合わせる必要が出てくると、サブクラスの数が爆発的に増える可能性があります。
- 例: 1つの基本クラスに対して、次のような拡張が必要だとします:
- 振る舞いA
- 振る舞いB
- 振る舞いAとBの両方
- この場合、サブクラスは3つ必要になります。
-
class BaseComponent def operation "Base" end end class ComponentWithBehaviorA < BaseComponent def operation "#{super} + A" end end class ComponentWithBehaviorB < BaseComponent def operation "#{super} + B" end end class ComponentWithBothBehaviors < BaseComponent def operation "#{super} + A + B" end end
- この場合、振る舞いが増えるたびに新しいサブクラスを作る必要があり、 組み合わせが増えるほどクラスの数が爆発的に増える(クラスの爆発) 問題が発生します。
- また、上記理由により動的に振る舞いを切り替えることが難しい。
-
デコレータパターンの場合
デコレータを使うと、振る舞いを1つのサブクラスにまとめ、それを動的に組み合わせることができます。
-
class BaseComponent def operation "Base" end end class BehaviorADecorator def initialize(component) @component = component end def operation "#{@component.operation} + A" end end class BehaviorBDecorator def initialize(component) @component = component end def operation "#{@component.operation} + B" end end # 動的に組み合わせ component = BaseComponent.new component_with_a = BehaviorADecorator.new(component) component_with_a_and_b = BehaviorBDecorator.new(component_with_a) puts component.operation # => "Base" puts component_with_a.operation # => "Base + A" puts component_with_a_and_b.operation # => "Base + A + B"
-
振る舞いの追加や切り替えが柔軟になり、サブクラスが増える問題を解消できる。
3. 技術や手法の”キモ”はどこにある?
1. オブジェクトの委譲:
- デコレータは、元のオブジェクトに機能を委譲します。
- これにより、元のクラスの振る舞いを変更せずに拡張が可能。
2. 動的な振る舞いの追加:
- インスタンス生成時にデコレータを適用するため、動的に機能を切り替えられる。
3. 柔軟性の向上:
- サブクラスを増やさずに振る舞いを拡張できるため、クラス設計が簡潔になります。
4. 実装例
例: メールのフォーマット
メール送信の際、デコレータを使ってコンテンツを装飾。
class PlainTextEmail
def content
"This is a plain email."
end
end
class SignatureDecorator
def initialize(email)
@email = email
end
def content
"#{@email.content}\n\n-- Ruby App"
end
end
# 利用例
email = SignatureDecorator.new(PlainTextEmail.new)
puts email.content # "This is a plain email.\n\n-- Ruby App"
- 実装ポイント: メール本文の装飾が柔軟に追加可能。
例3: サービス層のロギング
デコレータを使ってログを追加。
class Service
def execute
"Executing service"
end
end
class LoggingDecorator
def initialize(service)
@service = service
end
def execute
puts "#{Time.now} Log: Service started"
result = @service.execute
puts "#{Time.now} Log: Service ended"
result
end
end
# 利用例
service = LoggingDecorator.new(Service.new)
puts service.execute
# 2025-01-13 09:19:07 +0900 Log: Service started
# 2025-01-13 09:19:07 +0900 Log: Service ended
# "Executing service"
- 実装ポイント: サービスロジックを変更せずにログ機能を追加。
5. 議論はあるか?
メリット
- 動的に機能を追加できる。
- サブクラスの増加を防ぎ、コードのメンテナンス性を向上。
デメリット
- デコレータの多重適用が増えると、トラブルシューティングが難しくなる。
- 委譲の仕組みがコードを追いにくくする場合がある。
- デコレータパターンでは、元のオブジェクトに機能を追加するために「委譲」を使います。
- デコレータは、他のオブジェクト(元のオブジェクトや別のデコレータ)を保持し、そのオブジェクトに対して操作を委譲します。
- 例: シンプルな委譲。
- この仕組みでは、DecoratorがBaseComponentに操作を委譲しています。
-
class BaseComponent def operation "Base" end end class Decorator def initialize(component) @component = component end def operation "#{@component.operation} + Decorated" end end component = BaseComponent.new decorated_component = Decorator.new(component) puts decorated_component.operation # => "Base + Decorated"
- 例: デコレータが多層化した場合。
- この場合、decorated_componentがどのデコレータを持っていて、最終的な出力がどのように構成されているかを把握するのが難しくなります。
-
class BaseComponent def operation "Base" end end class BehaviorADecorator def initialize(component) @component = component end def operation "#{@component.operation} + A" end end class BehaviorBDecorator def initialize(component) @component = component end def operation "#{@component.operation} + B" end end # 元のオブジェクト component = BaseComponent.new # デコレータを適用 decorated_component = BehaviorBDecorator.new(BehaviorADecorator.new(component)) puts decorated_component.operation # => "Base + A + B"
議論
- デコレータパターンは、「柔軟性を優先したい場合」に最適です。
- ただし、使用箇所を明確にしないと、オーバーヘッドや可読性の低下につながる可能性があります。
6. まとめ
デコレータパターンは、既存のクラスに動的に振る舞いを追加するための強力なデザインパターンです。
その柔軟性を活かして、メンテナンス性が高く拡張可能なコードを実現しましょう。
ただし、過度な使用は可読性を損なうリスクがあるため、適切な範囲での利用を心がけることが重要です。
Discussion