今回は8章テンプレートパターンをC#で勉強していきます。
Head Firstデザインパターン
突然ですがコーヒーとお茶を淹れます
以下のようにコーヒークラスとお茶クラスを作ってprepareRecipeを呼べば淹れられそうです。
public class Coffee
{
public void prepareRecipe()
{
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void boilWater()
{
Console.WriteLine("Boiling water");
}
public void brewCoffeeGrinds()
{
Console.WriteLine("Dripping Coffee through filter");
}
public void pourInCup()
{
Console.WriteLine("Pouring into cup");
}
public void addSugarAndMilk()
{
Console.WriteLine("Adding Sugar and Milk");
}
}
public class Tea
{
public void prepareRecipe()
{
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
public void boilWater()
{
Console.WriteLine("Boiling water");
}
public void steepTeaBag()
{
Console.WriteLine("Steeping the tea");
}
public void addLemon()
{
Console.WriteLine("Adding Lemon");
}
public void pourInCup()
{
Console.WriteLine("Pouring into cup");
}
}
この二つのクラス、比べてみると似ているところが多くて無駄があるように感じませんか?
Templateパターンを使えば解消することができます。
Templateパターン
コーヒーとお茶はカフェイン入り飲料なのでカフェイン入り飲料という名前で抽象化してしまえば自分で好きな時にコーヒーでもお茶でも淹れることができるかもしれません。
二つのクラスで違うところと言えば
茶をこすのとコーヒーを淹れる部分、そしてレモンを入れるか砂糖ミルクを入れるかの部分です。
この違いがある部分は具体的なクラスを作るときに編集することにして、抽象化してしまいましょう。
public abstract class CaffeineBeverage
{
public void prepareRecipe()
{
boilWater();
brew();
pourInCup();
addCondiments();
}
public abstract void brew();
public abstract void addCondiments();
public void boilWater()
{
Console.WriteLine("Boiling water");
}
public void pourInCup()
{
Console.WriteLine("Pourint into cup");
}
}
abstractと書くことでこんなメソッドがありますよというのを伝えることができます。
このクラスをベースにコーヒークラスとお茶クラスを作ることができます。
public class Coffee : CaffeineBeverage
{
public override void brew()
{
Console.WriteLine("Dripping Coffee through filter");
}
public override void addCondiments()
{
Console.WriteLine("Adding Sugar and Milk");
}
}
public class Tea : CaffeineBeverage
{
public override void brew()
{
Console.WriteLine("Steeping the tea");
}
public override void addCondiments()
{
Console.WriteLine("Adding Lemon");
}
}
先ほどの例と比べてかなりすっきりしました。
すでに抽象クラスにあるメソッドを具体的なクラスを作るときにオーバーライドしてあげることで内容を変えることができます。
ブラックコーヒーかストレートティーが飲みたい
このままだとコーヒーには砂糖とミルクが、お茶にはレモンが乗ってしまいます。ブラックコーヒーを飲もうと思ったらBlackCoffee:CaffeineBevarageという形でクラスを別に作り直さなければいけないのでしょうか?
コンソールでユーザーに聞くという形で解消することができます。
public abstract class CaffeineBeverageWithHook
{
public void prepareRecipe()
{
boilWater();
brew();
pourInCup();
if (customerWantsCondiments())
{
addCondiments();
}
}
public abstract void brew();
public abstract void addCondiments();
public void boilWater()
{
Console.WriteLine("Boiling water");
}
public void pourInCup()
{
Console.WriteLine("Pourint into cup");
}
public virtual bool customerWantsCondiments()
{
return true;
}
}
customerWantsCondimentsがtrueの時にのみトッピングを足します。
public class CoffeeWithHook : CaffeineBeverageWithHook
{
public override void brew()
{
Console.WriteLine("Dripping Coffee through filter");
}
public override void addCondiments()
{
Console.WriteLine("Adding Sugar and Milk");
}
public override bool customerWantsCondiments()
{
string answer = getUserInput();
if (answer.ToLower().StartsWith("y"))
{
return true;
}
else
{
return false;
}
}
public string getUserInput()
{
string answer = null;
Console.WriteLine("Would you like milk and sugar with your coffee (y/n)?");
try
{
answer = Console.ReadLine();
}
catch(IOException e)
{
Console.Error.WriteLine("IO error trying to read your answer");
}
if(answer == "")
{
return "no";
}
return answer;
}
}
以上のように実際に実装することができます。