Chain Of Responsibilityパターン
概要
ある処理を実行したときに、クラスが自分の管轄外だと判断したら次のクラスにおまかせする。というのを繰り返すスタイル。(クラスじゃなくてもメソッドなどでもOK)
イメージは、トラブル発生時に
- アルバイト: 僕には対処できません…。社員さんお願いします。
- 社員: 私には対処できません…。店長お願いします。
- 店長: 私には対処できません…。エリアマネージャーお願いします
- エリアマネージャー: 了解。対応しましょう。
使い方
bad?
const handleOrder = (order: Order[]) => {
if (orders.length < 10) {
partTimeJob.make(orders);
} else if (orderCount < 50) {
staff.make(orders);
} else {
chef.make(orders);
}
}
このくらいの分岐ならifやcase文での分岐でいいかもしれないが、
分岐の条件が複雑になったり、分岐の数が増えたりすると可読性が一気に下がる
また、handleOrderの実装を変えたいってなったらhandleOrderを変えないといけなくなるため、結合度が高くなってしまっている
good
例1: クラスを使うパターン
abstract class Cook {
private nextCook: Cook | null = null;
setCook(cook: Cook) {
this.nextCook = cook;
return cook; // メソッドチェーンできるように返す
}
handle(orders: Order[]) {
if (this.canMake(orders)) {
this.make(orders);
} else if (this.nextCook) {
// 対処できない → 次の担当者に渡す
this.nextCook(orders);
} else {
console.log('failed to handle')
}
}
abstract canMake(orders: Order[]): boolean;
abstract make(orders: Order[]): void;
}
class PartTimeJob extends Cook {
canMake(orders: Order[]) {
return orders.length < 10
}
make(orders: Order[]) {
console.log('made by partTimeJob')
}
}
class Staff extends Cook {
canMake(orders: Order[]) {
return orders.length < 50
}
make(orders: Order[]) {
console.log('made by staff')
}
}
class Chef extends Cook {
canMake(orders: Order[]) {
return true
}
make(orders: Order[]) {
console.log('made by chef')
}
}
partTimeJob = new PartTimeJob()
staff = new Staff()
chef = new Chef()
partTimeJob.nextCook(staff).nextCook(chef)
partTimeJob.handle(orders)
例2: クラスを使わないパターン
// 成功したらtrue, 失敗したらfalse
type Handler = (orders: Order[]) => boolean;
const partTimeJobHandler: Handler = (orders) => {
if (orders < 10) return false;
console.log('made by partTimeJob')
return true
}
const staffHandler: Handler = (orders) => {
if (orders < 10) return false;
console.log('made by staff')
return true
}
const chefHandler: Handler = (orders) => {
console.log('made by chef')
return true
}
const chain = [partTimeJobHandler, staffHandler, chefHandler]
const handleOrders = (chain: Handlers, orders: Order[]) => {
for (const handler of chain) {
if (handler(orders)) return;
}
console.log("failed to handle");
}
この2つの例のようにそれぞれの処理の中で条件を書くことで、条件を可視化できる。
また、chainの組み合わせは自由に変えられるので、以下のようなケースになっても他のコックへの影響範囲を小さくすることができる
- staffが欠勤した
- 新しいstaffが追加された
よく使われる例はHTTPリクエストを捌く時。
使い所
- 処理の数や順序が変わりやすい時
- 処理の分岐が複雑なとき