Edited at

【OOP入門】続・Rubyでダックタイピングを理解する

以前に書いた 【OOP入門】Rubyでダックタイピングを理解するの補足です。

ダックタイピングを用いることで無駄なcase文やif文を追放することができますが、使用する上で多少注意すべきことがあります。

それは、 ダックタイピングを適応する側のオブジェクトは必ずメッセージに応答できる必要があること です。

例えば下記のようなダックがあったとします(およそのイメージ)。

class Communicate

# usersには下記のような感じで配列でJapanese, American, Germanのオブジェクトが入る
# [japanese, american, german]
def say_hello(users)
users.each(&:say_hello)
end
end

class Japanese
def say_hello
puts "こんにちは"
end
end

class American
def say_hello
puts "Hello"
end
end

class German
def say_hello
puts "Hallo"
end
end

この場合、各国の人々(Japanese, American, German)はそれぞれ say_helloというメソッドを備えているので、Communicateクラスのsay_helloメソッドの引数として渡されても応答できます。

が、このコードのどこかに「各国の人を表すクラスには必ずsay_helloのメソッドを実装しなさい」と書いてある訳ではありません。

明確なルールが無い状態で忖度して実装されています。

このように暗黙のルールの上に成り立っているコードでは実装漏れが発生する可能性があります。

例えば応答するメソッドが10個くらいにまで増えてくると、あるクラス(たとえばDutch)にはlaught_out_loadのメソッドを実装し忘れるかもしれません。

または、ある国の人には適応しないメソッドであるにも関わらず実装してしまうかもしれません(Frenchクラスの人は納豆を食べないけれどJapaneseクラスを参考にしてeat_nattoを実装してしまうかもしれません)。

*一人で開発している上では起こりにくかもしれませんが、チーム開発で複数の人が手を加えるシステムでは容易に発生します。

*また、そのコードが古くから存在する(&実装した人はとっくに退職している)場合には、そのコードが実装されている背景をコード以外から読み解くことは難しいため、明示的に実装がなされていない場合には容易に当初の意図とは外れた実装がなされます。

この問題を解決する方法の一つとして、モジュールを使用してインターフェースを強制する手法があります。

例えば、各国の人々のクラスに下記のモジュールをincludeするようにします。

module HumanBehavior

def say_hello
raise NotImplementedError
end
end

class Japanese
include HumanBehavior

def say_hello
puts "こんにちは"
end
end

class American
include HumanBehavior

def say_hello
puts "Hello"
end
end

class Germany
include HumanBehavior

def say_hello
puts "Hallo"
end
end

このように、実装すべきインターフェースの雛形をまとめたmoduleをincludeすることで、備えるべきインターフェースを明示すことができます。

*module側では共通で使用するインターフェースの雛形だけを用意し、詳細の実装はinclude先で実装すると、スッキリとしたmoduleになります。


合わせて読みたい