Commandパターン
概要
処理をCommand(関数やオブジェクト)としてラップすることで、処理の呼び出し側(Invoker)と処理内容(Receiver)を疎結合にできるパターン。
呼び出し側はCommandに向かって、実行しろと命令するだけでいい(具体的な処理を書かなくていい)ので、呼び出し側のコードをシンプルに保てる上に、疎結合になるためテストしやすくなる。
使い方
bad
type CookLevel = 'rare' | 'well-done' | 'medium'
type FishType = 'tuna' | 'salmon' | 'yellowtail'
class Shef {
cook(type: FoodType, ingredients: Ingredient[], options)
}
class Kitchen {
constructor(private shefs: Shef[]) {}
selectShef() {}
selectIngredients(type: FoodType) {}
cookSteak(cookLevel: CookLevel = 'medium') {
const shef = this.selectShef();
const ingredients = this.selectIngredients(type: 'steak')
shef.cook(type: 'steak', ingredients, { cookLevel })
}
cookSushi(fishType: FishType = 'tuna') {
const shef = this.selectShef();
const ingredients = this.selectIngredients(type: 'sushi')
shef.cook(type: 'sushi', ingredients, { fishType })
}
}
class Waiter {
constructor(private kitchen: Kitchen)
order(foodType: FoodType, options: { cookLevel?: CookLevel, fishType: FishType }) {
if (foodType == 'steak') {
this.kitchen.cookSteak(options.cookLevel);
} else if (foodType == 'sushi') {
this.kitchen.cookSushi(options.fishType);
} else {
...
}
}
}
const chef1 = new Chef();
const chef2 = new Chef();
const kitchen = new Kitchen([chef1, chef2]);
const waiter = new Waiter(kitchen);
waiter.order('steak', { cookLevel: 'rare' });
何を作るかという情報(foodType)をwaiterが知ってる必要がある。
もし、ramenなどのfoodTypeが増えるとwaiter側の分岐コードを変更する必要が出てくる
good
type CookLevel = 'rare' | 'well-done' | 'medium'
type FishType = 'tuna' | 'salmon' | 'yellowtail'
class Shef {
cook(type: FoodType, ingredients: Ingredient[])
}
class Kitchen {
constructor(private shefs: Shef[]) {}
selectShef() {}
selectIngredients(type: FoodType) {}
cookSteak(cookLevel: CookLevel = 'medium') {
const shef = this.selectShef();
const ingredients = this.selectIngredients(type: 'steak')
shef.cook(type: 'steak', ingredients, { cookLevel })
}
cookSushi(fishType: FishType = 'tuna') {
const shef = this.selectShef();
const ingredients = this.selectIngredients(type: 'sushi')
shef.cook(type: 'sushi', ingredients, { fishType })
}
}
interface Command {
execute: () => void;
}
class CookSteakCommand implements Command {
constructor(private kitchen Kitchen, private cookLevel: CookLevel) {}
execute() {
this.kitchen.cookSteak(this.cookLevel)
}
}
class CookSushiCommand implements Command{
constructor(private kitchen: Kitchen, private fishType: FishType) {}
execute() {
this.kitchen.cookSushi(this.fishType)
}
}
class Waiter {
order(command: Command) {
command.execute();
}
}
const chef1 = new Chef();
const chef2 = new Chef();
const kitchen = new Kitchen([chef1, chef2]);
const waiter = new Waiter();
const command = new CookSteakCommand(kitchen, 'rare')
waiter.order(command)
品物を作るという処理をCommandに切り出したことによって、何を作るかとその処理をorderに注入できるようになった
waiter側はorderのコマンドを実行するだけでよくなり、品物が増えた時にwaiterに対して変更をしなくて良くなった
使い所
- どの処理をするかの判断とその実行を別々で行いたいとき
- 処理自体に状態を持たせたくなった時