以下の記事を読むと、DCIはRubyでは遅いという指摘があったので、移譲で
実装すればいいと思ったので、下記記事のサンプルコードをもとに、
send/method_missing, forwardable, delegateを使ったサンプルを書いて
みました。
※ 上記記事では移譲を使ったアプローチにも言及しているし、改善するには
頑張らないといけないという程度の話と認識しました。
■ ベンチマークの結果(ruby 1.9.3p194)
without dci 3024223.4 (±1.7%) i/s - 15139474 in 5.007655s
with dci 768491.0 (±6.6%) i/s - 3833440 in 5.011732s
with dci(send) 1586461.1 (±2.1%) i/s - 7977320 in 5.030680s
with dci(forwardable)
1712079.6 (±1.8%) i/s - 8580546 in 5.013597s
with dci(delegate) 1134862.9 (±1.3%) i/s - 5701904 in 5.025205s
見にくくて済みません。コードは下の方にあります。
■ 思ったこと
元記事のベンチマークは、オブジェクトの生成コストくらいしかないような
処理にextendを突っ込んでいるため、そりゃ比較にならないだろうという
問題があります。
そもそも、処理が無いならDCIを入れる意味なんてほとんどないことを考えれば、
ここのコストは無視してもいいものなのですが、無視してもいいコストをいかに
削るかという、無駄に挑戦することも一興かなと思い、記事にしました。
fooメソッドの処理内容によっては(※)、extendを使った元記事のアプローチの
方が優秀な場合もあると思います。柔軟性などを考えれば、移譲の方が使い勝手が
いいだろうとも思いますが。
※ 単純な移譲にならずに、Double Dispatchになるとオーバーヘッドで遅くなる
可能性もあると思います。
さて、ベンチマーク結果に移ると、forwardableが意外と速い。先に書いた通り、
オブジェクト生成コストしかないような処理に対して、半分程度の速度低下で
DCIが実現できるということです。これは、RubyにおけるDCIの実用性を示す
ものではないでしょうか。
■ 余談
これ、DCIじゃなくてRoleObjectじゃんって突っ込みが入りそうですが、
DCIで設計したらRoleObjectで実装するのが定番かなと思ってます。あとは、
Decoratorかな。やったことないけど。
しかし、皆DCIに突っ込んでいくとか、勇敢すぎると思う。Coplienの名前が
出たら全力で逃げるところだろう。マルチパラダイムデザインの恐怖を
忘れたんだろうか。。。(ネタですよ。念のため。)
■ ソースコード
require 'rubygems'
require 'benchmark/ips'
require 'forwardable'
require 'delegate'
class ExampleClass
def foo; 42; end
end
module ExampleMixin
def foo; 43; end
end
class ExampleClassSend
def initialize(role)
@role = role
end
def method_missing(action, *args)
@role.__send__(action, *args)
end
end
class ExampleClassForwardable
extend Forwardable
def initialize(role)
@role = role
end
def_delegator :@role, :foo, :foo
end
class ExampleClassDelegate < SimpleDelegator
end
class ExampleRole
def foo
43;
end
end
@role = ExampleRole.new
Benchmark.ips do |bm|
bm.report("without dci") { ExampleClass.new.foo }
bm.report("with dci") do
obj = ExampleClass.new
obj.extend(ExampleMixin)
obj.foo
end
bm.report("with dci(send)") do
ExampleClassSend.new(@role).foo
end
bm.report("with dci(forwardable)") do
ExampleClassForwardable.new(@role).foo
end
bm.report("with dci(delegate)") do
ExampleClassDelegate.new(@role).foo
end
end