DCI(Data-Context-Interaction)からASI(Actor-Scene-Interaction)へ
あ、タイトルは釣りというか、なんかそれっぽいこと書きたかっただけです。ごめんなさい。
世間から3周くらい遅れてDCIというものの存在を知って、今さらネットで記事をあさって読んでたりします。この辺とか。
- http://d.hatena.ne.jp/digitalsoul/20100131/1264925022
- http://uehaj.hatenablog.com/entry/20100528/1275011951
- https://speakerdeck.com/kakutani/dci-sprk2012
- http://mikepackdev.com/blog_posts/24-the-right-way-to-code-dci-in-ruby
これらをいい加減に読んで、考えたのではなく感じたことをまとめると
- 従来よく利用されてきた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っていうのも考えた