Method Object

たくさんの引数や一時変数であふれてしまったメソッドを整理したい場合は、メソッド呼び出しを表現するオブジェクトを作成しましょう。

メソッドの整理に関するパターンです。
当初はシンプルだったメソッドでも、コードに手を入れていくうちに、行数や引数、一時変数などが増え、混乱した状態になってしまったという経験はありませんでしょうか。

class Obligation
   ...
  def send_task(task, job)
    not_processed = Array.new
    ...
    copied = Array.new
    ...
    executed = Array.new
    ...
  end
  ...
end

このようなメソッドを上手に整理したいと考えた場合は、どのようにするのが良いでしょうか。
その解の一つは、そのメソッド呼び出し自体を表現するオブジェクトを用意してしまうことです。
具体的には、以下のようにして実現します。

  • メソッドにちなんだ名前を持つクラスを定義*1
  • 元のメソッドが持っていた引数や一時変数、レシーバをインスタンス変数として用意
  • 元のレシーバと引数を受け取るConstructor Methodを用意
  • 元のメソッド本体をコピーしたcomputeメソッド*2を用意

例えば、先ほど例にしたコードは、以下のようにすることが出来ます。

class TaskSender
  def initialize(receiver, task, job)
    @receiver = reciever
    @task = task
    @job = job
    @not_processed = Array.new
    @copied = Array.new
    @executed = Array.new
  end

  def compute
    ...
  end
end

class Obligation
  ...
  def send_task(task, job)
    TaskSender.new(self, task, job).compute
  end
  ...
end

このようにすると、メソッドが実現していた責務とそのためにどれだけコストをかけていたのかがより明らかになるので、本パターンを用いることはコードの見通しを良くさせると共に、データ構造や処理を洗練させていく良い入り口ともなります。

*1:通常のオブジェクトは名前が名詞になりますが、Method Objectの場合は振る舞い自体を表現しているため名前が動詞になります。

*2:ここでは引用元であるケント・ベックのSmalltalkベストプラクティス・パターン―シンプル・デザインへの宝石集に沿ってcomputeメソッドという名前を使用していますが、executeとかactionといった名前で使われていることもあります