DCI(Data-Context-Interaction)からASI(Actor-Scene-Interaction)へ

あ、タイトルは釣りというか、なんかそれっぽいこと書きたかっただけです。ごめんなさい。

世間から3周くらい遅れてDCIというものの存在を知って、今さらネットで記事をあさって読んでたりします。この辺とか。

これらをいい加減に読んで、考えたのではなく感じたことをまとめると

  • 従来よく利用されてきたMVCというアーキテクチャだとユーザーのメンタルモデルと齟齬が出ますねと
  • あと一つのモデルでいろんなユースケースに対応させると当然モデルが肥大化しますしねと
  • なので、ユーザーに直接モデルをイメージさせるのではなく、モデルの一歩手前によりユーザー視点に近いロールって言うのを一段かませることで齟齬を少なくして、責務も分散しましょうよと
  • で、そのロールって結局何かと言うとシステムが利用される特定のコンテキストにおいてユーザーがモデルに期待する役割ですよと
  • もうすこし具体的に書くと、ロールはモデルの持つ粒度の細かいメソッドを組み合わせて、ユーザーがイメージするような処理をそのまま呼び出せるようにするものですよと
  • Rubyで実装するには、ロールをモジュールとして定義して、ひとまとまりの処理ごと定義されるコンテキストの中で、モデルにロールをextendして、そのextendされたメソッドを使って実際の処理を行いますよと。(この文だとわかりにくいと思うので詳細についてはリンク先参照)

こんな感じ。ナナメ読み+妄想で書いてるので間違ってるかもだけど。

これ、いくつかモヤモヤポイントがあって、まず

1. ロールモジュールをextendしてるけど、これいいのん?

せっかくコンテキストとかいうクラス作ってそこでロールに求められる処理をするようにしてるのに、コンテキストから出てもモデルにロールが残ってるって言うのはなんかちょっと・・・。まぁ将来的にはRefinementとか使うのかも知れないけど。ちなみに他の言語だと例えばScalaならTraitで、C++ならtemplate使うとかいう話。

でもこれそもそもロールがモデルをインスタンス変数として保持して使うAdapterみたいな実装だとなにか問題でもあるんだろうか?ロールからモデルのプライベートなインスタンス変数やメソッドにアクセスできないのが問題とか?でもそもそもロールから利用される機能ってプライベートにするのが適切かどうか個人的には疑問だし・・・。

例えば先の例だとロールの実装と利用例は

module Customer
  def add_to_cart(book)
    self.cart << book
  end
end
user.extend Customer

ざっくりこんな感じなんだけど

class Customer
  attr_accessor :user
  def add_to_cart(book)
    @user.cart << book
  end
end
customer = Customer.new
customer.user = user

少なくともRubyで組む限りこれでいいような。なんか素直だし、無駄にUserを汚染しない分だけ。

2. コンテキストってコンテキストなん?

モデルとロールを結びつけるものをコンテキストと呼んでるんだけど、なんとなーく違和感が。いや、言わんとすることは分からんでもないけど。

ちなみに例によって例の実装と利用例はこんな感じ。

class AddToCartContext
  attr_reader :user, :book

  def self.call(user, book)
    AddToCartContext.new(user, book).call
  end

  def initialize(user, book)
    @user, @book = user, book
    @user.extend Customer
  end

  def call
    @user.add_to_cart(@book)
  end
end
context = AddToCartContext.new(user, book)
context.call

決められたインターフェースを持つオブジェクト(user, book)を明示的に追加(initialize)して、決められた手段(call)で発火される処理を「コンテキスト」と呼ぶことに感じるこの違和感。そもそもコンテキストをcall(executeでもrunでも同じだけど)するってどういうことなのよ。それプロシージャですやん。コンテキストって言うのはもっと自由でなんというか救われてなきゃあダメなんだ・・・。

・・・

ということでじゃあなんと呼べば納得できるのかを考えた結果、「Scene」っていうのを思いつきました*1。で、それならいっそ用語を演劇系で統一してDataもActorと呼ぶのはどうだろうと。まとめるとASI(Actor-Scene-Interaction)。

実装はこう。(親クラスは役割を示すためだけに指定してます)

class User < Actor
  attr_accessor :cart
  def initialize
    cart = []
  end
end

class Customer < Role
  attr_accessor :user

  def initialize(user)
    @user = user
  end

  def add_to_cart(book)
    @user.cart << book
  end
end

class Scene
  def self.play(casts)
    scene = self.new
    casts.each do |actor, role|
      scene.introduce actor, role
    end
    scene.play
  end

  def initialize
    @casts = {}
  end

  def introduce(actor, role)
    @casts[role] = role.is_a?(Symbol) ? actor : role.new actor
  end
end

class AddToCartScene < Scene
  def user
    @casts[Customer]
  end

  def book
    @casts[:book]
  end

  def play
    user.add_to_cart book
  end
end

んで使い方はこう。

user = User.new
book = {title:'Google Wave入門', author:'あんどうやすし'}

scene = AddToCartScene.new
scene.introduce user, Customer
scene.introduce book, :book
scene.shoot

舞台(AddToCartScene)を作って、そこに役者(User)と役柄(Customer)を登場(introduce)させると、役者は役柄を果たす準備をして(role.new(actor))、準備ができたら演技開始(scene.play)、みたいな。本質的にはDCIと同じものなはず。DCIの本質とか知らないけど。ということでこれからはDCIじゃなくてASIっすよ。

さぁ殺せ。

*1:あとStageっていうのも考えた