Duck Typing
最近コードと書く上で使っていきたいなと思ったので勉強。
Duck Typing (ダックタイピング)とは
- 定義
結論から書くと、「あるオブジェクトがアヒルのように歩き、アヒルのように話すなら、Rubyインタプリタはそのオブジェクトをアヒル」とみなす。この考え方をダックタイピングといいます。
- クラス≠型
ダックタイピングの背景には、「クラス≠型」という考え方があります。
Java 等、静的型付け言語では「オブジェクトの型=そのオブジェクトのクラス」という考えが一般的です。しかし、Ruby では(Javaであっても)、オブジェクトの型は、その「オブジェクトが何ができる」かによって定義されます。
説明の文を書いてもいまいちピンとこないと思いますので、コードを混ぜて考えていきます。
Duci Typing を用いたCSV パーサーのテスト
- CSVParser のテスト
例えば、Rails 内で次のようなCSV パーサークラスがあるとします。
require 'csv' class CSVParser def self.parse(str_or_readable) CSV::Reader.create(str_or_readable).collect end end
これをテストするとなると、以下のようなコードになると思います。
require File.dirname(__FILE__) + '/../test_helper' class CSVParserTest < Test::Unit::TestCase def test_parser expected = [["1","2","3"], ["4","5","6"]] file = fixture_file_upload('csv_test/duck_typing.csv', 'text/plain') assert_equal expected, CSVParser.parse(file) end end
fixture_file_upload は、Rails のテストヘルパーで、ファイルのアップロードをシミュレートします。これをparse メソッドに渡せば、アップロードされたファイルをこのメソッドに渡すことのシミュレートになるというすんぽうです。
しかし、このテストはファイルが絡んでいるため、結構面倒です。このテストのために、CSV ファイルを生成する必要もあります。もし、エラーデータのテストをしたいと言う際にも、わざわざCSV ファイルを作るはめになります。
- Duck Typing を用いる
ここで、Duck Typing の出番です。Duck Typing の考え方にそえば、テストにわざわざ本物のファイルを用意しなくてもいいということになります。その代わりに、「ファイルのように歩き、ファイルのように話す何か」があればいいのです。
ここで、テストを次のように書き換えました。
def test_parser file = StringIO.new(<<-DATA) 1,2,3 4,5,6 DATA expected = [["1","2","3"], ["4","5","6"]] assert_equal expected, CSVParser.parse(file) end
テストは通過します。これでOK というのがDuck Typing です。StringIOクラスは、IOと「同じインターフェース」を持つ文字列クラスです。つまり、外から見ればFile と同じように振舞うのです。
これなら、テストデータをテスト内に書くことができ、わざわざファイルを作成する必要はありません。
Duck Typing を使ったコーディング
Duck Typing はテストだけでなく、実際のコードにも役立ちます。例えば、次のメソッドは、「<<」を持つオブジェクトなら、全てを引数として渡すことができます。
class User def initialize(name) @name = name end def add_name_to_list(list) list << @name end end
- テスト
require 'test/unit' class UserTest < Test::Unit::TestCase def test_add_name_to_list user = User.new("bob") #listは配列 list = [] user.add_name_to_list(list) assert_eqaul ["#{user.name}"], list #listは文字列 list = "" user.add_name_to_list(list) assert_eqaul user.name, list #listはファイル filepath = "hoge".txt File.open(filepath,'r') do |f| user.add_name_to_list(f) end File.open(filepath,'r') do |f| assert_eqaul user.name, f.gets end end end
Duck Typing は、オブジェクト指向とマッチした概念だと思います。つまりこの場合、各クラス(Array, String, File)が「<<」というメソッドを「自身に何かを追加する」という操作として定義しておけば、User#add_name_to_list は、引数に関わらず、自身の名前をリストに追加するという定義どおりに動くということです。
RailsのソースにみるDuck Typing
- activerecord-1.15.3\lib\active_record\connection_adapters\abstract\quoting.rb
module ActiveRecord module ConnectionAdapters # :nodoc: module Quoting # Quotes the column value to help prevent # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection]. def quote(value, column = nil) # records are quoted as their primary key return value.quoted_id if value.respond_to?(:quoted_id) case value when String, ActiveSupport::Multibyte::Chars
ここで、quote メソッドの2行目にrespond_to? がでてきます。これは、レシーバに引数に指定されたメソッドが定義されているかどうかを調べるメソッドです。
つまり、quoted_id という振る舞いをするならということです。Duck Typing じゃなく書けば、次のようになります。
return value.quoted_id if value.is_a?(ActiveRecord::Base)
しかし、これだと、ActiveRecord::Base クラスしか受け入れられない柔軟でないメソッドになってしまいます。respond_to? を使うほうがいいでしょう。次のように書けば、そのメリットが分かるかと思います。これなら、引数としてFile, StringIO などが渡せます。File に限定しないことで、先ほどのようなテストもかけますし、実際役に立つと思います。
def hoge(obj) if obj.respond_to?(:read) ・・・ else raise end end
これだと、メソッドに制限がなくバグの原因にならないかという質問に対しては、大抵は問題にならないと述べられています。それ以上にメリットの方が多いと。
Let's Duck Typing!!
参考
- 作者: Dave Thomas,Chad Fowler,Andy Hunt,まつもとゆきひろ,田和勝
- 出版社/メーカー: オーム社
- 発売日: 2006/08/26
- メディア: 大型本
- 購入: 7人 クリック: 270回
- この商品を含むブログ (152件) を見る