この記事で紹介するパターン
デコレータ(Decorator) パターン
どんなデザインパターン?
・ある既存のクラスに対して機能追加を行うためのパターン
・既存のクラスに対しては手を入れない
既存クラスに手を入れず機能追加を行う方法は?
・デコレータクラスを作って既存クラスのインスタンスを渡し、デコレータクラスに機能追加や機能拡張を書いていく
と書いてみたものの、ウェキペディアにわかりやすい?説明があった。
Decorator パターンの方針は、既存のオブジェクトを新しい Decorator オブジェクトでラップすることである。 その方法として、Decorator のコンストラクタの引数でラップ対象の Component オブジェクトを読み込み、コンストラクタの内部でそのオブジェクトをメンバに設定することが一般的である。
何が良いのか?
・上で何度も行っているとおりで既存のクラスに手を入れなくても良い点
Rubyのコードで簡単な例を紹介してみる
登場人物
・Dogクラス(手を入れたい既存のクラス)
・DogDecoratorクラス(Dogクラスに機能を追加するためのクラス)
Dogクラス
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Dog def initialize(name) @name = name end def name @name end def bark 'ワンワン' end end |
現状ワンワンと鳴くインスタンスメソッドが定義されているのみ。
次にDogクラスの機能を追加するためのデコレータクラスを準備する
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class DogDecorator def initialize(dog) @dog = dog end def name @dog.name end def bark @dog.bark end end |
Dogオブジェクトを保持し、barkメソッドやnameメソッドを@dog.barkや@dog.nameに移譲しているのがポイント。
これで、Dogオブジェクト(既存のクラス)と同等の振る舞いをするオブジェクトを作れた。
コンソールで試してみると以下のとおり。
1 2 3 4 5 6 7 8 9 10 11 12 |
[51] pry(main)> dog = Dog.new("poti") => # [52] pry(main)> dog.name => "poti" [53] pry(main)> dog.bark => "ワンワン" [54] pry(main)> dog_decorator = DogDecorator.new(dog) => #> [58] pry(main)> dog_decorator.name => "poti" [59] pry(main)> dog_decorator.bark => "ワンワン" |
ここからはデコレータクラスにメソッドを追加したり、メソッドの振る舞いを変えたり好きなようにすればよい。
Dogクラスはそのままで、デコレータクラスを以下のようにする
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
class DogDecorator def initialize(dog) @dog = dog end def name @dog.name end # nameも言うように機能拡張 def bark @dog.bark + "#{name}だよ" end # 歩くメソッドを追加 def walk 'とことこ' end end |
コンソールで試してみる。
1 2 3 4 5 6 |
[68] pry(main)> dog_decorator = DogDecorator.new(dog) => #> [69] pry(main)> dog_decorator.bark => "ワンワンpotiだよ" [70] pry(main)> dog_decorator.walk => "とことこ" |
機能追加、機能拡張できた。
まとめ
・Dogクラスに手を入れることなく、Dogクラスに機能を追加できる
・もっと良いコードの例があった気がするがまあこれで良いとする
・Railsでファットモデルに対する対策としてhttps://github.com/amatsuda/active_decorator
などを入れてビューの表示に関するメソッドをデコレータクラスで管理したりをよくするが、おそらくこのデコレータパターンを使っている(と思う)