オブジェクト志向設計実践ガイドを読んでダックタイピングを使って抽象度を上げる方法を学んだので、整理してみるφ(..)
オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方
- 作者: Sandi Metz,?山泰基
- 出版社/メーカー: 技術評論社
- 発売日: 2016/09/02
- メディア: 大型本
- この商品を含むブログ (1件) を見る
はじめに
突然ですが以下のようなコードを見て、どのように思いますか?
class EmploymentHandler def work(employees) employees.each do |employee| case employee when Staff then employee.do_clean_up when Manager then employee.do_check end end end end class Staff def do_clean_up # ... end end class Manager def do_check # ... end end
渡された引数によってcase文
使い、引数に渡されたクラスによって実行する処理を分けています。
一見良さそうに見えますが、このコードには以下のような問題が含まれています・・・。
EmploymentHandler.work
メソッドに渡されるクラスが増える度にwhen...then
を追加しなければならない。EmploymentHandler.work
メソッドを修正する場合に、呼び出すStaff
,Manager
のメソッドについても理解しなければならない。
今回のようなケースでは、ダックタイピングを用いることで、EmploymentHandler.work
の抽象度を上げ、他Classの修正から守り、よりわかりやすいコードとすることが出来ます!
ダックタイピングとは
ダックタイピングは、下記のような考え方です(._.)
「もしオブジェクトがダック(アヒル)のように鳴き、アヒルのように歩くならば、そのクラスが何であれ、それはダックである」
オブジェクト指向実践ガイドより抜粋
つまり、渡された引数がなんであれ、同様のメソッドを持っていれば同じものとして扱い、中身の処理は関知しないということでしょうか。
ダックタイピングを使ってみる
では実際にダックタイピングを使ってコードをリファクタリングしてみようと思います!
今回のケースであれば、Staff.do_clean_up
とManager.do_check
というメソッドをEmploymentHandler.work
の中で呼び出していますが、社員または管理者がどのような働きをするかを定義しています。
なので、まずは既存のメソッドをラップするようなStaff.work
、Manager.work
というメソッドをつくってみましょう。
class EmploymentHandler def work(employees) employees.each do |employee| case employee when Staff then employee.work when Manager then employee.work end end end end class Staff def work do_clean_up end def do_clean_up # ... end end class Manager def work do_check end def do_check # ... end end
どうでしょうか、case
文内でClassによって処理は分岐していますが実行されるメソッドはemployee.work
となっています!
引数と実行されるメソッドが同じになったので、ダックタイピングが使えそうですね!!
さっそくダックタイピングを使ってリファクタリングしたコードが下記ですφ(..)
class EmploymentHandler def work(employees) employees.each do |employee| employee.work end end end class Staff def work do_clean_up end def do_clean_up # ... end end class Manager def work do_check end def do_check # ... end end
リファクタリング前後でコードを比較してみると、リファクタリング後の方が抽象度が上がり変更に強くなっていますね!!
修正前 | 修正後 |
---|---|
EmploymentHandler.work メソッドに渡されるクラスが増える度にwhen...then を追加しなければならない。 |
work メソッドを持つクラスを渡すというルールを守れば、どのクラスでもEmploymentHandler.work を使うことが出来る。 |
EmploymentHandler.work メソッドを修正する場合に、呼び出すStaff ,Manager のメソッドについても理解しなければならない。 |
EmploymentHandler.work は、work メソッドが実装されていることを信頼し、呼び出し元のクラスのことは知る必要がない。 |
ダックタイピングをテストする
最後にダックタイピングのテストについて考えてみようと思います。
ダックタイピングのテストに必要なことは、それぞれのクラスについて特定の同名メソッドが実装されているかどうかを検証する必要があります。
今回は、Staff
及びManager
にwork
メソッドが実装されているかを検証します。※テストはminitest
を使用しています。
require 'minitest/autorun' # workが実装されているか検証するモジュール module WorkInterfaceTest def test_implements_the_work_inmterface assert_respond_to(@object, :work) end end # 社員用のテスト class StaffTest < MiniTest::Unit::TestCase include WorkInterfaceTest def setup @staff = @object = Staff.new end end # 管理者用のテスト class ManagerTest < MiniTest::Unit::TestCase include WorkInterfaceTest def setup @manager = @object = Manager.new end end
WorkInterfaceTest
モジュールをStaffTest
及びManagerTest
にincludeすることにより、それぞれのクラスにwork
が実装されていることを検証しています。
またwork
の実装有無をWorkInterfaceModuleTest
で検証することにより、新しいクラスが追加されても容易にテストを追加出来るようになっていますφ(..)
まとめ
私もまだまだ理解が不足している点もあるのですが、簡単にダックタイピングについてまとめて見ました!
今回のケースではcase...when
を使っている箇所をダックタイピングでリファクタリングしてみましたが、responds_to?
、kind_of?
等を使っている場合も使えるそうですφ(..)
ダックタイピングを上手く活用出来ると綺麗で、変更に強いコードを書けそうなので、使えそうな部分では積極的に活用して身に付けていきたいと思いました。