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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/202653/a3a891ed-be98-ce9e-d357-7236e55d312c.png)

[オブジェクト指向設計実践ガイド ~Rubyでわかる 進化しつづける柔軟なアプリケーションの育て方](https://af.moshimo.com/af/c/click?a_id=1022937&p_id=170&pc_id=185&pl_id=4062&s_v=b5Rz2P0601xu&url=http%3A%2F%2Fwww.amazon.co.jp%2Fexec%2Fobidos%2FASIN%2F477418361X)を読んで、フックメッセージについて学習したので解説します。

## フックメッセージとは？

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

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

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

### 悪い例

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/202653/f1928459-47ba-dda7-9657-119baa3da375.png)

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

```ruby: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
```

```ruby: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
```

```ruby: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```を送り忘れたとしましょう。

```ruby: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```を送り忘れることは十分に考えられるのでこの問題に対処しなければいけません。

### いい例

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

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

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

```ruby: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

```

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

```ruby: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_initialize```と```local_spares```をスーパークラスに定義だけして置いて、```merge```などの処理はスーパークラスに記述して起きます。
同時にサブクラスでは具体的な処理をオーバーライドすることで、実現したい実装を行なっています。

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