頑張るときはいつも今

自称エンジニアのゴリラです。日々精進。

【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