ずっと君のターン

2013-03-08 花粉の影響が出始めた

DCI Hansei Meetup

| 00:58 |  DCI Hansei Meetup - ずっと君のターン を含むブックマーク

本家のDCI Meetup出てないんだけどなぜか反省して来ました。とりあえず発表資料。

みなさんわりとガチなOO老害トークしてる中で一人だけこんなんですいません。でも私にはこういうのが求められている気がしたんです。だからこれは使命感のなせる業なんです。

2013-01-28 快晴

顧客が本当に必要だったDCI

| 22:06 | 顧客が本当に必要だったDCI - ずっと君のターン を含むブックマーク

顧客が説明した要件

f:id:technohippy:20130128220240p:image


アーキテクトのDCI

http://rubysource.com/dci-the-evolution-of-the-object-oriented-paradigm/

class TransferringMoney
  include Context

  def self.transfer source_account_id, destination_account_id, amount
    source = Account.find(source_account_id)
    destination = Account.find(destination_account_id)
    TransferringMoney.new(source, destination).transfer amount
  end 

  attr_reader :source_account, :destination_account
  def initialize source_account, destination_account
    @source_account = source_account.extend SourceAccount
    @destination_account = destination_account.extend DestinationAccount
  end 

  def transfer amount
    in_context do
      source_account.transfer_out amount
    end 
  end 

  module SourceAccount
    include ContextAccssor

    def transfer_out amount
      raise "Insufficient funds" if balance < amount
      decrease_balance amount
      context.destination_account.transfer_in amount
      update_log "Transferred out", amount
    end
  end

  module DestinationAccount
    include ContextAccssor

    def transfer_in amount
      increase_balance amount
      update_log "Transferred in", amount
    end
  end
end


プログラマのMVC

class TransferringMoney
  def self.transfer source_account_id, destination_account_id, amount
    source = Account.find(source_account_id)
    destination = Account.find(destination_account_id)
    TransferringMoney.new(source, destination).transfer amount
  end

  def initialize source_account, destination_account
    @source_account = source_account
    @destination_account = destination_account
  end

  def transfer amount
    transfer_out amount
  end

private

  def transfer_out amount
    raise "Insufficient funds" if balance < amount
    @source_account.decrease_balance amount
    transfer_in amount
    @source_account.update_log "Transferred out", amount
  end

  def transfer_in amount
    @destination_account.increase_balance amount
    @destination_account.update_log "Transferred in", amount
  end
end


顧客が本当に必要だった関数

def transfer(source_account_id, destination_account_id, amount)
  source = Account.find(source_account_id)
  destination = Account.find(destination_account_id)

  source.decrease_balance amount
  destination.increase_balance amount
  destination.update_log "Transferred in", amount
  source.update_log "Transferred out", amount
end

・・・

DCIのサンプルって「関数」でよくない?って思うようなの多いよね、と・・・。

big brother albania 6big brother albania 6 2013/04/11 19:53 どうもありがとうございました

2013-01-23 雪が降るってホント?

DCI Meetupに参加

| 23:12 |  DCI Meetupに参加 - ずっと君のターン を含むブックマーク

・・・してません。

してないけど、想像で思いの丈を書くよ。

まず、DCIについてはちょっと前にモノ思ったんだけど、togetter見る限り、疑問の一つに関してDCI Meetup一応の回答が得られた模様。

その疑問というのは「ロールはextend Moduleではなくて、Adapterで実装してはいかんのか?」ということなんだけど、回答としては要するに「ロールが異なっても同じオブジェクトなんだからアイデンティティを同一に保ちたい」ということらしい。

ただ、言わんとすることは分かったけど、実はあんまり納得できてなくて、とあるコンテキストで何かしらのロールを果たしているオブジェクトのアイデンティティってそんなに重要なんだろうか?

ミッキーマウスがステージで踊ってるのを見るときに中の人が誰かなんてどうでもいいというか、むしろ中の人なんていないことにして見るのが正解であるように、ロールを果たしているオブジェクトが実際には何か、なんてむしろ考えずに済むのが理想だし、そのためのコンテキストじゃないかと思うんだけど。

あと例えば、とあるコンテキストにロールが二つ登場するとして、実際にはそれらの実体が同じオブジェクトになるということもあり得ると思う。そうした場合に、アイデンティティが同じということは「オブジェクト自身」はコンテキスト内で自分がどのロールを果たしているかを知らないということになる。

もちろんプログラマはそのオブジェクトを参照している変数名でロールを知ることができるんだろうけど、オブジェクト自身は普通自分がどういう変数名で参照されているかなんてわからないわけだから、自分のロールを知る術がない*1。このことは「リフレクション重要」っていうCoplien自身の言葉とずれがあるように思う。



DCIは人間讃歌

| 23:12 |  DCIは人間讃歌 - ずっと君のターン を含むブックマーク

ただここまで書いといてアレだけど、DCIについては実装方法とかは瑣末な話で、もともと アレグザンダーとかその辺が大本にあって、目指すところはもうちょっと電波な思想的な話みたい。DCI Meetupでは「DCIは世界を変える方法」とか言ったみたいだけど、まぁ要するに世界の見方を変えようって話なのかな。

これ(DCI)は単なるソフトウェアフレームワークではないし、メンタルモデルをとらえるためのパターンランゲージに留まるものでもない。コンピュータソフトウェアにおける人間的要素を賛美するような世界観をとらえるパラダイムなのである。

http://dl.dropbox.com/u/15791171/sa10-JimCoplien_Patterns_ja.pdf

これまでのOO設計は静的な構造を捉えることばかりに囚われていて、ユーザーがそのシステムをどのように捉えているのかと視点に欠けている部分があった。アーキテクチャからメンタルモデルへ、つまり「世界がどういう形であるか」から「世界がどういう形であるとユーザーは考えているのか」へ設計の目指すところを変えたのがDCIだと思う。

ユーザーのメンタルモデルという視点にたてば、それは固定されたものではなく、ユースケースごとに変化するものであるはず。と言っても完全に不定では何が何だか分からない。システムにはこれまで同様静的な部分も当然ある。この一時的な構造と、永続的な構造を分かりやすく分離するために導入されたのが、ロールでありコンテキストだろう。

つまり、実際のシステムのコアになる部分は静的・永続的なものであって、オブジェクト(データ)はそこに属する。ここまではこれまでと変わらない。一方ユースケースごとにユーザーの中に構築されるメンタルモデルは動的・一時的で、そこではオブジェクトがコンテキストに応じて様々なロールに射影されている。この部分がこれまでのMVCには欠けていたもので、DCIによって新しく導入されるもの。

敢えて視線をイデアから人間の住む世界である洞窟の影へ。これがDCIは「コンピュータソフトウェアにおける人間的要素を賛美するような世界観をとらえるパラダイム」という言葉の意味じゃないだろうか。

要するにDCIは人間讃歌で勇気の讃歌。

*1:extendによる実装ではobj.my_role みたいなメソッドを実現する手段がないということです

2013-01-11 寒いね

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

| 23:00 |  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っていうのも考えた