decorator, presenter, exhibit という3つの実装パターンについて

@a_suenami さんのこのツイートの Decorator, Presenter, Exhibit が気になったので調べてみた。


結果、以下にまとまっていたので、記事中のコードを引用しながら色々と考えてみる。
http://mikepackdev.com/blog_posts/31-exhibit-vs-presenter





Decorator とは?

Decorator とは、おそらく GoFの DecoratorPattern のことだと思う。
元記事では最初の方に Car クラスで説明されている。

以下は元記事のコードだが、見れば大体分かると思う。

# ruby の SimpleDelegator によって Car のメソッドを delegate している。
class Decorator < SimpleDelegator
end

class Car
  def price
    1_000_000
  end
end

class CarWithHeatedSeats < Decorator
  #Car.price を CarWithHeatedSeats.price でオーバーライドしている
  def price
    super + 5_000
  end
end

car = Car.new
car.price #=> 1000000

#Car を CarWithHeatedSeats でラップしている。
#ラップに関しては ruby の SimpleDelegator がいい感じにやってくれている。
car = CarWithHeatedSeats.new(car)
car.price #=> 1005000

CarWithHeatedSeats で Car をラップすることで、
本来の Car.price の挙動に任意の挙動を加えることができる。
上記の例だと +5000 されているのが任意の挙動にあたる。

さらに、目的によっては以下のように Car に存在しないメソッドを実装して、
Car を拡張したオブジェクトを作ることもできる。

class CarWithHeatedSeats < Decorator
  #Car.price を CarWithHeatedSeats.price でオーバーライドしている
  def price
    super + 5_000
  end

  # Car を拡張するメソッド
  def hello
  	#省略
  end

end


目的別に CarWithHeatedSeats のような Decorator を作って Car をラップすれば、
Car を目的別に拡張することができる。
継承を利用しないオブジェクトの拡張手段といったところだろうか。

Presenter とは?

Presenter は "表示に関するロジック" を責務に持つ実装パターンであり、
結構一般的なパターンだと思う。

元記事では以下のように description という表示に関するロジックを持ったメソッドを実装している。

class CarPresenter < Decorator
  def description
    if price > 500_000
      "Expensive!"
    else
      "Cheap!"
    end
  end
end

car = CarPresenter.new(Car.new)
car.price #=> 1000000
car.description => "Expensive!"



"表示に関するロジック" とはなんだろう?
具体的には以下が該当するかなと思っている。

  • User クラスの status の値に応じて "プレミアム会員", "一般会員" のように文言を出し分ける。
  • int の金額をカンマ区切りの文字列にする。
  • User クラスの name に "様" を付ける。

こういった Viwe 周りのロジックはサーバサイドのビジネスロジック自体にはあまり関係ないかもしれないが、
ユーザーに見せる部分なので色々と気を使って加工することが多い。

これらをモデルである Car クラスに全て実装してもいいが、
Car クラスはどんどん肥大化するだろう。
この肥大化を避けたい場合、 Presenter を導入すると良い。

Exhibit とは?

Exhibit は "描画" を責務に持つ実装パターンである。

以下は記事中の Exhibit のコード。
何をやっているかは見れば大体分かると思う。

class CarWithTwoDoorsExhibit < Decorator
  def initialize(car, context)
    @context = context
    super(car) # Set up delegation
  end

  def additional_info
    "Some cars with 2 doors have a back seat, some don't. Brilliant."
  end

  def render
    @context.render(self)
  end
end

class TextRenderer
  def render(car)
    "A shiny car! #{car.additional_info}"
  end
end

class HtmlRenderer
  def render(car)
    "A <strong>shiny</strong> car! <em>#{car.additional_info}</em>"
  end
end

car = CarWithTwoDoorsExhibit.new(Car.new, TextRenderer.new)
car.render #=> "A shiny car! Some cars with 2 doors have a back seat, some don't. Brilliant."
car.price #=> 1000000

car2 = CarWithTwoDoorsExhibit.new(Car.new, HtmlRenderer.new)
car2.render #=> "A <strong>shiny</strong> car! <em>Some cars with 2 doors have a back seat, some don't. Brilliant.</em>"

CarWithTwoDoorsExhibit は特定の文言を テキスト or HTML で表示することができる。
表示する文言も additional_info() にて定義されている。
文言表示には Car が持っている情報が必要なので、コンストラクタでは Car を引数にとっている。

これを見たときに、
フルスタックな WAF についている HTML Helper, View Helper 的な印象を受けた。

自分は CakePHP を使った経験があるが、
以下のように HTML に PHP のタグを書いて form を表示していた。

<?php echo $this->Form->create($article); ?>


PHPの例は HTML の form を生成するためのヘルパーであるが、
"create() の引数に $article というオブジェクトを渡すことで、特定の HTML を描画する"
という性質は Exhibit に近いものがあるのではないか? と思っている。

Presenter と Exhibit の違いは?

  • Presenter = "表示に関するロジック" を責務に持つ
  • Exhibit = "描画" を責務に持つ

と説明したが、言葉で説明すると違いが抽象的になってしまい、なかなか難しい。

Presenter のセクションで以下は Presenter が持つべきロジックであると紹介しているが、

  • User クラスの name に "様" を付ける。

「"様" を付けるのはロジックというよりは、"描画" の責務ではないのか?」
と反論されると、
「そうかもしれない」と感じてしまう。

しかし、このような "値を加工するロジック" を Exhibit の責務にしてしまうと、
Presenter が持つべきロジックがかなり限定されてしまい、
Exhibit が肥大化してしまう可能性が高くなる。
個人的には Presenter に実装するだろう。

ということで、個人的な感覚になってしまって申し訳ないが、
Presenter は View に表示する変数を加工するための責務を持ち、
Exhibit はその変数を利用する View 周りのパーツ(HTMLのタグや文章)を描画する責務を持つイメージがある。

これは "View を描画する" という仕組みを考えたときに、
責務としてどう分割するか? を考えた結果である。
ココらへんの責務に関しては "責務を分ける必要性" というセクションでも触れるので、
そちらも読んで欲しい。

Decorator, Presenter, Exhibit の関係性

関係性としては以下になる。

  • Decorator = オブジェクトを拡張する実装パターン
  • Presenter = "表示に関するロジック" を責務に持つ Decorator
  • Exhibit = "描画" を責務に持つ Decorator

Presenter も Exhibit も Decorator という実装パターンを利用しているという点でいうと同じであるが、責務は異なる。

Presenter + Exhibit

Presenter, Exhibit はそれぞれ異なる責務を持つので、
当然ながら組み合わせることができる。

以下は記事中のコードだが、見れば大体分かるはず。
Presenter が内部に Exhibit を保持しており、
render() が利用できるようになっている。

class CarPresenter < Decorator
  def initialize(car)
    exhibit = rand(2) == 1 ? CarWithTwoDoorsExhibit : CarWithFourDoorsExhibit
    super(exhibit.new(car, TextRenderer.new))
  end

  def description
    if price > 500_000
      "Expensive!"
    else
      "Cheap!"
    end
  end
end

car = CarPresenter.new(Car.new)
car.description #=> "Expensive!"
car.render #=> "A shiny car! ..."

この CarPresenter は以下の機能を持つ。

  1. description() にて特定の文言を出し分ける。
  2. render() にて特定のテキストを出し分ける。

ユースケースとしては、CarPresenter を View に渡して、View 内で利用する想定なのだろうか?
以下は PHP のコードだが、こんなイメージ?

<html>
	<?php $car_presenter.description() ?>
	<?php $car_presenter.render() ?>
</html>



記事中には載っていないが、Exhibit が Presenter を持てば、
Presenter で加工した値を Exhibit が利用できるようになる。

以下は CarExhibit が CarPresenter を持ち、
render() にて CarPresenter.price() を利用するパターン。
個人的にこちらの方がしっくりくる。

#ruby書けないのでシンタックス間違っているかもしれません・・・。
class CarExhibit
  def initialize(car_presenter)
  	@car_presenter = car_presenter
  end

  def render
  	"this is " + @car_presenter.price()
  end
end



責務を分ける必要性

Presenter, Exhibit はそれぞれ別の責務を持ち、組み合わせることができると説明したが、
そもそも Presenter, Exhibit で責務を分ける必要はあるのだろうか?

直前のセクションで以下のように Exhibit に Presenter を持たせていたが、

class CarExhibit
  def initialize(car_presenter)
  	@car_presenter = car_presenter
  end

  def render
  	"this is " + @car_presenter.price()
  end
end



以下のようにPresenter.price() のロジックを直接 Exhibit に持たせてもいいのではないか?
こうすれば、わざわざ Presenter を実装する必要はなくなる。
Exhibit は "描画" を責務に持つので、描画に必要なメソッド(今回は render() になる)のみ公開している。
price() のようなロジックは private なメソッドにした方が良いだろう。

class CarExhibit
  #コンストラクタには Car クラスを渡す
  def initialize(car)
  	@car = car
  end

  def render
  	"this is " + price()
  end

  private
    def price
    	#CarPresenter.price() 相当のロジックを実装する
      @car.price() + 5000
    end
end



結論から言うと、 "描画する" という目的を達成するのであれば問題ないと思う。
わざわざ CarPresenter を用意する必要はなさそう。

では、Exhibit にロジックを実装すれば Presenter は不要なのか?
というと、そうでもない。
Presenter を用意することによって View で利用する値を各所で使い回しやすくなる。

例えば、
User クラスの status の値に応じて "プレミアム会員", "一般会員" のように文言を出し分ける
という仕様があった場合、
"プレミアム会員", "一般会員" のような文言は複数の画面で表示する必要があるかもしれない。

その場合、 Exhibit で実装してしまうと、文言を出し分けるロジックが複数の Exhibit でコピペされてしまう可能性がある。

具体的なコードで説明すると以下の price() のようなロジックが、

class CarExhibit
  #コンストラクタには Car クラスを渡す
  def initialize(car)
  	@car = car
  end

  def render
  	"this is " + price()
  end

  private
    def price
  	  #CarPresenter.price() 相当のロジックを実装する
      @car + 5000
    end
end


以下のように各 Exhibit にコピペされる可能性がある。

class XxxExhibit
  #省略

  def render
  	"Xxx is " + price()
  end
  
  private
    def price
      @car + 5000
    end
end

class YyyExhibit
  #省略
  
  def render
  	"<b>Yyy is " + price() + "</b>"
  end

  private
    def price
      @car + 5000
    end
end


Exhibit は "描画" の責務を持つので、
render() によって出力される文字列を利用するのが目的となる。
そして、render() には HTML のタグのような "特定のページでしか利用しない装飾" を扱う可能性があるので、
Exhibit 同士を組み合わせることが難しいケースがある。
そういった場合、安易にロジックをコピペすることで解決してしまうかもしれない。

では、 private になっている price() を public にしたらどうだろう?
public にすれば、Exhibit 同士を組み合わせることでコピペを防ぐことができる。

以下のように XxxExhibit にて CarExhibit.price() を参照するイメージ。

class XxxExhibit
  #コンストラクタに CarExhibit を渡す
  def initialize(car_exhibit)
  	@car_exhibit = car_exhibit
  end

  def render
  	"Xxx is " + @car_exhibit.price()
  end
  
end



しかし、CarExhibit には render() のように描画の責務を持つメソッドが実装されているので
CarExhibit を XxxExhibit に渡してしまうと、
"price() で算出される値のみ利用したい" という目的に対して、
CarExhibit の責務が大きすぎる気がする。

例えば、以下のように CarExhibit.render() を利用することも可能になってしまう。

class XxxExhibit
  #コンストラクタに CarExhibit を渡す
  def initialize(car_exhibit)
  	@car_exhibit = car_exhibit
  end

  def render
  	"Xxx is " + @car_exhibit.price()
  end

  #render() が利用できてしまう
  def xxx
  	"Xxx is " + @car_exhibit.render()
  end
  
end

一見便利そうに見えるが、
実際に Exhibit が持つメソッドは render(), price() だけとは限らない。
Car が持つそれぞれの値に対して price() のようなロジックを持つことも考えられる。

Exhibit オブジェクトが大きくなればなるほど想定外の依存が発生する可能性は高くなる。
「特定の Exhibit の特定のメソッドを修正したら想定外の Exhibit に影響してしまった」
なんてことがありえるかもしれない。

Exhibit のロジックを Presenter に切り出せば、各オブジェクトの責務は小さくなる。
基本的にクラスや関数は責務が限定されればされるほど再利用性が上がり、
修正による影響を予想しやすくなるので、
Exhibit にロジックを実装するのが常に正しいとは限らない。

このように Exhibit, Presenter で責務を分けるかどうか? というのはケースバイケースである。
自分たちのケースに合った最適な実装を選択しよう。

Presenter, Exhibit を導入する必要性

ここまで Presenter, Exhibit について説明してきたが、
そもそもこれらのパターンは必要なのだろうか? というのも考えなければいけない。

開発方針に依存するが、
サーバサイドで HTML をレンダリングするようなWebサービスでない限り、
View 周りの値の装飾や加工はクライアント側がやってくれるかもしれない。
その場合、サーバが View 周りのロジックを持つ機会というのは減ってくるだろう。

Presenter, Exhibit をわざわざ導入するまでもないケースだって当然ありえるはずなので、
そもそも必要なんだっけ? というのも考える必要がある。

ちょっとした加工であれば、
Controller でチャチャッとやってしまうというのもありだろう。
それが問題になったときに Presenter, Exhibit を導入すればいい。

ちなみに、自分は以下のように View 用の値を全て詰め込むオブジェクトを用意して、

package response

type UserListPage Struct {
	ID int
	Name string
}



モデルからそのオブジェクトを生成するような関数を実装するだけで済ますことが多い。
最初はこの程度で十分やっていけたりするし、
この程度であれば Controller にべた書きしてもいいと思う。

func NewUserListPage(user *User) *UserListPage {
	return &UserListPage {
		ID: user.ID,
		Name: user.Name + " 様", //ここで値の加工をしてしまう
	}
}



値の加工ロジックが複数箇所で必要になる場合は、
そのロジックを関数に切り出してまとめてしまう。

func NewUserListPage(user *User) *UserListPage {
	return &UserListPage {
		ID: user.ID,
		Name: displayUserName(user.Name), //切り出した関数を利用する
	}
}

//関数に切り出す
func displayUserName(name string) string {
	return name + " 様"
}



しかし、切り出し対象の関数が多いと、
切り出した関数に Prefix, Saffix が多くなってきて管理が辛くなってくることがある。
そうなると、 Presenter 相当のオブジェクトで管理したくなってくる。
1つのモデルに対して1つの Presenter を用意する感じ。

func NewUserListPage(user *User) *UserListPage {
	p := NewUserPresenter(user)
	return &UserListPage {
		ID: p.ID,
		Name: p.Name, //UserPresenter を利用する
	}
}

type UserPresenter struct {
	ID int
	Name string
}

func NewUserPresenter(user *User) *UserPresenter {
	//省略	
}



上記は Decorator を利用しているわけではないのだが、
NewUserListPage() という関数が Exhibit のような責務(= 描画の責務)を持っていて、
UserPresenter が Presenter のような責務(= 表示に関するロジック)を持っている感じになっている。

このように具体的な実装方法は異なっていても、
結局 "描画" と "表示に関するロジック" という責務に分ける感じになるので、
Presenter, Exhibit 相当のものを必要に応じて好みの実装方法で導入していけばいいと思っている。

まとめ

特にないです。

記事中では "View を描画する" という HTML を描画するようなケースを前提の内容でしたが、
JSON でも値の加工は必要なので、考え方自体は変わらないかなという感じです。

"すえなみチャンス" というのを聞いて "早川チャンス" を思い出しました。
全然別物なのですが、懐かしい気持ちになりました。

ruby のコードは雰囲気で書いたので、おそらく間違っていると思います。
なんとなーくなイメージが伝われば十分かなーと思い、横着してしまいました。

マサカリあれば、 twitter or ブログのコメント欄に連絡いただければと思います。