Skip to content

Instantly share code, notes, and snippets.

@r7kamura
Last active December 10, 2015 09:18
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save r7kamura/4412949 to your computer and use it in GitHub Desktop.
Save r7kamura/4412949 to your computer and use it in GitHub Desktop.
Exhibit Pattern in MVC

Exhibit Pattern in MVC

Rails等のMVC構造の中でExhibit Patternを用いる場合の一例。

Why

Exhibit Patternを何故用いるのか。

  1. 問題: MVCの中でも特にViewは煩雑になりがち
  2. 原因: Viewの中に複数の責任が同居しているからである
  3. 方針: ある側面でViewを捉え、特定の責任を別の層に切り分けるべきである
  4. 対策: Modelを描画するという責任を、Exhibit Patternを用いて切り分けよう

What

Exhibit Patternとは何か。

ModelをViewのcontextの中で描画する責任を負うもので、Decoratorの一種である。 描画することに特化しており、Viewについての知識はほとんど持っていない (例えばrenderというメッセージに対応できるものである、程度の知識は持つ)。 Modelはデータをどう扱うかというビジネスロジックに関する責任を持ち、 Exhibitはデータをどう描画するかという責任を持つ。

Decorator Pattern

Wikipediaより

Decorator パターンの方針は、既存のオブジェクトを新しい Decorator オブジェクトでラップすることである。
その方法として、Decorator のコンストラクタの引数でラップ対象の Component オブジェクトを読み込み、
コンストラクタの内部でそのオブジェクトをメンバに設定することが一般的である。
Decorator パターンは、既存のクラスを拡張する際にクラスの継承の代替手段として用いられる。
継承がコンパイル時に機能を拡張するのに対し、Decorator パターンはプログラムの実行時に機能追加をする点が異なる。

Contract

Decoratorは、内包するデータに対して透過的でなければならず、Exhibitについても同様である。 つまり、Modelが対応できるメッセージには、Exhibitも同様に対応できる必要がある。 この制約を守るための実装として、DecoratorはしばしばProxyとして実装されることが多い。 Rubyでは、method_missingやSimpleDelegatorを利用してこれを実現できる。

Presenter Pattern

Presenterも同じくDecoratorの一種であり、これもViewとModelの両方の知識を持つが、 PresenterはViewの複雑なロジックを引き受ける責任を負う。 Exhibitに比べるとView寄りであり、Viewについての知識をより多く持つ。

Multiple Exibits

ExhibitとModelが必ず1対1でなければいけないということは無い。 表現したいものにより複数のExhibitが存在するはずで、多対多の関係を持つ。 これにより、複数のModelに共通する描画ロジックを1つのExhibitで表現でき、 また1つのModelが複数のExhibitを持てる。

Helper

例えばRailsには、Viewのロジック全般に責任を持つHelperという層が存在するが、雑多な責任は全てHelperに詰め込まれることになる。 Exhibitを導入することで、Modelの描画に関する責任を取り除くことができ、Helperの肥大化を解消できると考えられる。 Exhibit導入後のHelperは、特定のModelに紐付かない描画処理のためのロジックを請け負う責任を持つ。 (例: HTML5のfigureタグを描画するロジック)

Where

Exhibit用のファイルをどこに置くか。

Railsの場合、app/{models,views, controllers} が標準で用意されている。 Objects on Railsでは、app/exhibits/*.rbというファイルを用意している。 Exhibitはアプリケーションに特化した層なので、app/exhibits以下に置くのは妥当と言える。

When

Exhibitをいつ利用するか。

Modelを特定のロジックに従って描画したいという要求が発生したときに利用する。 例えば、Modelの状態によって異なるテンプレートを描画したいとき、 条件分岐によってこれを実現することになるが、 このロジックをExhibit PatternによりViewから分離することが出来る。

How

Exhibitをどう実現するか。

例えば、Modelの状態(persisted?メッセージを送った結果)によって、描画するテンプレートを分岐させる。 まずSimpleDelegatorを利用してコンストラクタで受け取ったmodelに全てのメソッドを委譲する。 Viewのコンテキストを受け取り、条件に応じてrenderメソッドを呼ぶ(Double Dispatch Pattern)。 form_for等に利用されたときのことを考慮して、ActiveModelとしてのAPIを壊さないように気を付ける。

class Exibit < SimpleDelegator
  def initialize(model)
    super(model)
  end

  def render(view_context)
    if persisted?
      view_context.render(...)
    else
      view_context.render(...)
    end
  end

  # for ActiveModel API constraint
  def to_model
    __get_obj__
  end

  # for ActiveModel API constraint
  def class
    to_model.class
  end
end

<%= Exhibit.new(@entry).render(self) %>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment