「HeadFirstデザインパターン」と「Rubyによるデザインパターン」を読んで Template Method パターン


何番煎じか判りませんがお勉強メモを残します

HeadFirstデザインパターン」第8章
Rubyによるデザインパターン」第3章

Template Method パターン

コーヒーと紅茶を作るクラスが例として使用されていた

コーヒーを作る手順

  • お湯を沸かす
  • ドリップする
  • カップに注ぐ
  • 砂糖とミルクを追加する

紅茶を作る手順

  • お湯を沸かす
  • 紅茶を浸す
  • カップに注ぐ
  • レモンを追加する

「HeadFirstデザインパターン」でのJavaコードは(だいたい)こんな感じ

public class Coffee {
  void prepareRecipe() {
    boilWater();
    brewCoffeeGrinds();
    pourInCup();
    addSugarAndMilk();
  }

  public void boilWater() {
    // お湯を沸かす
  }

  public void brewCoffeeGrinds() {
    // ドリップする
  }

  public void pourInCup() {}
    // カップに注ぐ
  }

  public void addSugarAndmilk() {
    // 砂糖とミルクを追加する
  }
}
public class Tea {
  void prepareRecipe() {
    boilWater();
    steepTeaBag();
    pourInCup();
    addLemon();
  }

  public void boilWater() {
    // お湯を沸かす
  }

  public void steepTeaBag() {
    // 紅茶を浸す
  }

  public void pourInCup() {}
    // カップに注ぐ
  }

  public void addLemon() {
    // レモンを追加
  }
}

できた!けどなんか臭う

boilWater(お湯を沸かす) と pourInCup(カップに注ぐ) が重複している
スーパークラスにまとてみよう

public abstract class CaffeinBeverage {
  abstract void prepareRecipe();

  void boilWater() {
    // お湯を沸かす
  }

  void pourInCup() {}
    // カップに注ぐ
  }
}
public class Coffee extends CaffeinBeverage {
  final void prepareRecipe() {
    boilWater();
    brewCoffeeGrinds();
    pourInCup();
    addSugarAndMilk();
  }

  public void brewCoffeeGrinds() {
    // ドリップする
  }

  public void addSugarAndMilk() {
    // 砂糖とミルクを追加する
  }
}
public class Tea extends CaffeinBeverage {
  final void prepareRecipe() {
    boilWater();
    steepTeaBag();
    pourInCup();
    addLemon();
  }

  public void steepTeaBag() {
    // 紅茶を浸す
  }

  public void addLemon() {
    // レモンを追加する
  }
}

できた!けどまだなんか臭う

コーヒーも紅茶も

  • お湯を沸かす
  • コーヒーか紅茶の成分をお湯に抽出する
  • カップに注ぐ
  • 飲み物に適当なトッピングを追加する

どっちも、ほぼ同じ手順を踏んでるじゃないか
いまこそ Template Method の出番だ!

public abstract class CaffeinBeverage {
  final void prepareRecipe() {
    boilWater();
    brew();
    pourInCup();
    addCondiments();
  }

  void boilWater() {
    // お湯を沸かす
  }

  abstract void brew(); // サブクラスに実装、対処させる

  void pourInCup() {}
    // カップに注ぐ
  }

  abstract void addCondiments(); // サブクラスに実装、対処させる
}
public class Tea extends CaffeinBeverage {
  public void brew() {
    // 紅茶を浸す
  }

  public void addCondiments() {
    // レモンを追加する
  }
}
public class Coffee extends CaffeinBeverage {
  public void brew() {
    // ドリップする
  }

  public void addCondiments() {
    // 砂糖とミルクを追加する
  }
}

Rubyではどうなるんや?

class CaffeinBeverage
  def prepare_recipe
    boil_water
    brew
    pour_in_cup
    add_condiments
  end

  def boil_water
    p 'お湯を沸かす'
  end

  def pour_in_cup
    p 'カップに注ぐ'
  end

  # サブクラスでオーバーライドしてもしなくても自由パターン
  # def brew
  # end

  # サブクラスでオーバーライド強制パターン(まぁ無くても NoMethodError 辺りになってくれるだろうけど)
  # def brew
  #   raise 'Called abstract method'
  # end
end
class Tea < CaffeinBeverage
  def brew
    p '紅茶を浸す'
  end

  def add_condiments
    p 'レモンを追加する'
  end
end
class Coffee < CaffeinBeverage
  def brew
    p 'ドリップする'
  end

  def add_condiments
    p '砂糖とミルクを追加する'
  end
end
Coffee.new.prepare_recipe
# =>
"お湯を沸かす"
"ドリップする"
"カップに注ぐ"
"砂糖とミルクを追加する"

Tea.new.prepare_recipe
# =>
"お湯を沸かす"
"紅茶を浸す"
"カップに注ぐ"
"レモンを追加する"

「おぉ〜!Rubyぽい!」とかそういう事はなかった

Template Method パターンはスーパークラスに手順(メソッドの呼び出し方、順番)を定義し、いくつかのメソッドはサブクラスで実装、オーバーライドする
共通処理をスーパークラスに実装するのではなく、決まっている手順をスーパークラスで実装し、それぞれのメソッドはサブクラスで実装しよう ということ
大まかな処理の実行の流れを制御しつつ、各処理の細かい実装はユーザに任されている そんなフレームワークを作成する設計ツールとして頻繁に見かけるらしい

しかし、このパターンは継承を使用している。
あなたが本当にやりたかった事は継承を使ったこの方法でなければ実装できなかった事なのか?
もしかして、他に選択肢はあったんじゃないのか?