【Ruby】オブジェクト思考設計実践ガイドを読み始めた#5
記事に書くこと
前回に引き続き、オブジェクト思考設計ガイドを読み進めています。 5章で学んだことをまとめます。
先にまとめ
ダックタイピング
は同様のメソッドを持っていればどんなオブジェクトでも同じように扱える- クラスの種類ごとに処理を分岐しているケースでは
ダックタイピング
を使って依存度を下げる
ダックタイピングって?
なんだかpythonを勉強した時にも読んだ記憶がありますが、
いまいち使いどころが分からないままだった奴です。
"If it walks like a duck and quacks like a duck, it must be a duck" (もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルに違いない)
どの本にも出てくる言葉ですよね。
ダックタイピング自体は、オブジェクトのクラスがなんであろうと、同様のメソッドを持っていれば、
同じものとして扱うようなプログラミングスタイルだと思っています。
コードから考えた方が、理解しやすいと思います。
まずは、ダックタイピングではない下のコードから。
class EmploymentWorker def work(employees) employees.each do |employee| case employee when Staff then employee.do_clean when Managager then employee.do_check end end end class Staff def do_clean # ~~~~~~~ end end class Managager def do_check #~~~~~~~~~~ end end
EmploymentWorker#work
では、Staff
もしくはManager
が格納された配列を受け取っているようです。
さらに、引数で渡された配列を順にwhen ~ case
で処理を分岐させています。
上の規模感的には問題無いように見えますが、拡張性とかを含めると下記の問題があります。
case ~ when
に依存しすぎている- 操作対象のクラスが増えるたびに、
when
を増やして行かないといけない
- 操作対象のクラスが増えるたびに、
- メッセージの送り先にも依存しすぎている
- クラスごとに、呼び出し先のメソッドを
EmploymentWorker
を知っている(依存する)必要がある - 依存先のオブジェクトに変更があった際には
EmploymentWorker
も変更する必要が出てくる
- クラスごとに、呼び出し先のメソッドを
とはいえ、EmplomentWorker
は複数のクラスに対して操作するできるような実装をしなければいけません。
どんなオブジェクトであっても、同様のメソッドを持っていれば、同じものとして扱えるようにリファクタしていきたいと思います。
EmploymentWorker
からすれば、どのクラスであってもwork
してくれればいいという書き方にリファクタした物が下記のコードです。
class EmploymentWorker def work(employees) employees.each do |employee| employee.work end end end class Staff def work do_clean end private def do_clean #~~~~~~~ end end class Managager def work do_check end private def do_check #~~~~~~~~~~ end end
EmploymentWorker
の依存度が一気に減った気がします。
ダックタイピングを使うことで、依存先の抽象度を高くすることで、
後からでも拡張しやすい構造にリファクタすることできました。
修正前 | 修正後 |
---|---|
扱えるオブジェクトが増えるたびに、case~when を増やす必要があった |
work メソッドさえ実装していればどんなオブジェクトも扱える |
扱うオブジェクトのメソッドまでEmploymentWorker は知っている必要があった |
具体的なメソッドは呼び出し先に移譲することで、work さえ知っていればよくなった |
ダックタイプをどうやって見つけるのか?
ダックタイプを使うことで依存度を減らせることが分かりました。
ただ、実際にダックタイプをどのように見つけていくのか?
書籍では、後術の3通りパターンにはダックタイプに置き換えることができるように書かれていました。
パターン1 クラスで分岐するcase文
上でダックタイプの例で書いたコードがこのパターンです。
class EmploymentWorker def work(employees) employees.each do |employee| case employee when Staff then employee.do_clean when Managager then employee.do_check end end end class Staff def do_clean # ~~~~~~~ end end class Managager def do_check #~~~~~~~~~~ end end
EmploymentWorker
からすれば、どのオブジェクトであっても、work
してくれればいいという形で置き換えることができます。
パターン2 kind_of?とis_a?を使うパターン
オブジェクトのクラスを確認する方法で、パターン1ではwhen case
を使っていました。
when case
ではなくても、kind_of?
やis_a?
でもクラスを確認することができます。
これらを利用して処理を分岐しているパターンにおいてもダックタイピングで置き換えることができます。
class EmploymentWorker def work(employees) employees.each do |employee| if employee.kind_of?(Staff) #~~~~ elsif employee.kind_of?(Managager) #~~~ else #~~~ end end end end
パターン3 respond_to?
respond_to?
は、あるオブジェクトがメソッドを持っているか(実装しているか)を判定するメソッドです。
先ほどのパターンまでは、クラスに依存していましたがメソッドに依存して分岐しているケースでも、
ダックタイプに置き換えることができます。
employees.each do |employee| if employee.respond_to?(:do_clean) #~~~~ elsif employee.respond_to?(:do_check) #~~~ else #~~~ end end