この記事は「オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方」を読んだまとめです。
ダックタイピング
ダックタイピングはいかなる特定のクラスとも結びつかないパブリックなインターフェース
「もしもオブジェクトがダックのように鳴き、ダックのように歩くのなら、そのクラスは何であれ、それはダックである」というのが名前の由来。
パブリックインターフェースをクラスから切り離して抽象的にする。
何であるかではなく、何をするかによって定義される抽象的な型をを見つける。隠された抽象的なインターフェースを見つけることがダックタイピングにつながりクラス間の依存を取り除き柔軟な設計につながる。
class Trip
attr_reader:bicycles,:customers,:vehicle
#この'mechanic'引数はどんなクラスのものでもよい。
def prepare(mechanic)
mechanic.prepare_bicycles(bicycles)
end
end
class Mechanic
def prepare_bicycles(bicycles)
bicycles.each{|bicycle|prepare_bicycle(bicycle)}
end
def prepare_bicycle(bicycle)
#...
end
end
Tripのprepareメソッドはprepare_bicyclesに応答できるオブジェクトに依存している。旅行の準備がメカニックにメッセージを送り自転車を準備するだけであれば問題はないが、準備工程が増えた時にこのままでは以下のようになり、Tripクラスは3つのクラス名と具体的なメソッドメソッドを知ってしまい依存してしまう。
Tripはメカニックにしかメッセージを送らないと想定していると、工程が増えたときに行き詰まってしまう。
class Trip
attr_reader:bicycles,:customers,:vehicle
def prepare(preparers)
preparers.each{|preparer|
case preparer
when Mechanic
preparer.prepare_bicycles(bicycles)
when TripCoordinator
preparer.buy_food(customers)
when Driver
preparer.gas_up(vehicle)
preparer.fill_water_tank(vehicle)
end
}
end
end
class TripCoordinator
def buy_food(customers)
#...
end
end
class Driver
defgas_up(vehicle)
#...
end
def fill_water_tank(vehicle)
#...
end
end
依存を取り除くための鍵となるのは、「Tripのprepareメソッドは単一の目的を果たすためにあるので、その引数も単一の目的を共に達成するために渡されてくるということを認識すること」です。
どの引数も同じ理由のためにここに存在し、その理由自体は引数の背後にあるクラスとは関係しません。
MechanicもDriverもTripCoordinatorもそれぞれの役割は異なるが目的は、「旅行の準備をする者」である。この発想からダックタイプを生み出すことができる。
この視点で考えればprepare_trip(旅行の準備をする)振る舞いをするのが、「旅行の準備をする者」と言い換えることができる。
抽象的な「旅行の準備をする者」というインターフェースを想定して、prepare_tripを実装しているものは「旅行の準備をする者」というダックタイプができる。MechanicもDriverもTripCoordinatorも「旅行の準備をする者」として振る舞う必要がある。
class Trip
attr_reader:bicycles,:customers,:vehicle
def prepare(preparers)
preparers.each{|preparer|
preparer.preparer_trip(self)
end
end
class Mechanic
def prepare_trip(trip)
trip.bicycles.each{|bicycle|
prepare_bicycle(bicycle)}
end
end
class TripCoordinator
def prepare_trip(trip)
buy_food(trip.customers)
end
end
設計をどこまで抽象的にするかはトレードオフ。ifとクラス名で分岐させて具体的にコーディングすれば必要な時間は少なく済むだろうしぱっとはわかりやすい。しかし今後の拡張、変更にコストがかかる。抽象化は手間はかかるが今後の拡張、変更は容易になる。