6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

フックメッセージを使って親クラスと子クラスの結合を弱くする方法

Posted at

image.png

オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方を読んで、フックメッセージについて学習したので解説します。

フックメッセージとは?

サブクラスがそれに合致するメソッドを実装することで情報を提供できるようにするための専門のメソッド

フックメッセージを使う場面の例

抽象的な説明をされても全く理解できないと思うので、実際の例を使って解説していきます。

悪い例

ありえそうな悪い例から解説を始めていきます。

image.png

継承関係は図のようになっています。

bicycle.rb
class Bicycle
  attr_reader :size, :chain, :tire_size

  def initialize(args={})
    @size = args[:size]
    @chain = args[:chain] || default_chain
    @tire_size = args[:tire_size] || default_tire_size
  end

  def spares
    {
      tire_size: tire_size,
      chain: chain
    }
  end

  def default_chain
    '10-speed'
  end

  def default_tire_size
    raise NotInmplementedError
  end
end
road_bike.rb
class RoadBike < Bicycle
  attr_reader :tape_color

  def initialize(args)
    @tape_color = args[:tape_color]
    # 親クラスがinitializeに反応するということを知っている -> 依存
    super(args)
  end

  def spares
    # 親クラスでHashを返すsparesが実装されているといことを知っている -> 依存
    super.merge({ tape_color: tape_color })
  end

  def default_tire_size # <- サブクラスの初期値
    '23'
  end
end
mountain_bike.rb
class MountainBike < Bicycle
  attr_reader :front_shock, :rear_shock

  def initialize(args)
    @front_shock = args[:front_shock]
    @rear_shock = args[:rear_shock]
    # 親クラスがinitializeに反応するということを知っている -> 依存
    super(args)
  end

  def spares
    # 親クラスでHashを返すsparesが実装されているといことを知っている -> 依存
    super.merge({ rear_shock: rear_shock })
  end

  def default_tire_size # <- サブクラスの初期値
    '2.1'
  end
end

spares メソッドとinitializeメソッドに注目してください。
コードを見るとわかるように親クラスに依存していることがわかると思います。

もし新しいサブクラスRecumbentBikeが追加された時に、プログラマーがinitialize内でsuperを送り忘れたとしましょう。

recumbent_bike.rb
class RecumbentBike < Bicycle
  attr_reader :flag

  def initialize(args)
    @flag = args[:flag]
    # ここでsuperを送るのを忘れていた
  end

  def spares
    super.merge({ flag: flag })
  end

  def default_tire_size
    '28'
  end
end

bent = RecumbentBike.new(flag: 'tall and orange')
bent.spares
# => {tire_size => nil, <- 初期化されていない
#     chain     => nil,
#     flag      => 'tall and orange'
#    }

super の送り忘れによってエラーの原因とは遠く離れたところで起こる可能性があります。その時のデバッグは大変なものになります。

他にもsparesメソッド内でsuperを送り忘れると、ハッシュの形が間違ってしまい、エラーが起こります。

Bicycle クラスとそのサブクラスの設計に携わったプログラマーならこのようなミスを起こすことは少ないかもしれませんが、全く設計に携わっていないプログラマーが今回のようにRecumbentBikeを追加した時にエラーが起こる可能性が上がります。
どんなプログラマーでもsuperを送り忘れることは十分に考えられるのでこの問題に対処しなければいけません。

いい例

悪い例のようなミスを避けるためにフックメッセージを使います。

サブクラスがそれに合致するメソッドを実装することで情報を提供できるようにするための専門のメソッド

フックメッセージとは上記のような意味でしたが、実際にはどのように使われるのか下のコードを見てください。

bicycle.rb
class Bicycle
  attr_reader :size, :chain, :tire_size

  def initialize(args={})
    @size = args[:size]
    @chain = args[:chain] || default_chain
    @tire_size = args[:tire_size] || default_tire_size

+   post_initialize(args) # フックメッセージを送る
  end

+ def post_initialize(args)
+   nil # サブクラスでオーバライドするためのメソッドを定義する
+ end

  def spares
    {
      tire_size: tire_size,
      chain: chain
    }
+   .merge(local_spares)
  end

+ def local_spares
+   # サブクラスでオーバーライドするためのメソッドを定義
+   {}
+ end


  def default_chain
    '10-speed'
  end

  def default_tire_size
    raise NotInmplementedError
  end
end

RoadBikeMountainBikeでコードは変わらないのでRoadBikeだけを書きます。

road_bike.rb
class RoadBike < Bicycle
  attr_reader :tape_color

+ def post_initialize(args)
+   # サブクラス側でフックメッセージを受け取りオーバーライドする
+   @tape_color = args[:tape_color]
+ end

+ def local_spares
+   # さっきまでsuper.mergeしていた部分を置き換える
+   # サブクラスでオーバライドする
+   { tape_color: tape_color }
+ end

  def default_tire_size # <- サブクラスの初期値
    '23'
  end
end

このようにすることでサブクラスでsuperしなくてもよくなりました。

  • サブクラスのBicycleへの結合度が減った
  • サブクラスがsuperを送らなくてもよくなった
  • サブクラスが「スーパークラスがsparesメソッドを実装していること」を知らないようになった

まとめ

ここまでくるとフックメッセージの

サブクラスがそれに合致するメソッドを実装することで情報を提供できるようにするための専門のメソッド

という意味がわかったのではないでしょうか?
今回の例では、post_initializelocal_sparesをスーパークラスに定義だけして置いて、mergeなどの処理はスーパークラスに記述して起きます。
同時にサブクラスでは具体的な処理をオーバーライドすることで、実現したい実装を行なっています。

フックメッセージを使うことによって、コードの見通しも良くなり、後からサブクラスを追加するプログラマーにとっても親切なものとなりました。

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?