Strategyパターン
「何をするか (what)」と「どうやるか (how)」を分離することで、実行時に後者を差し替え可能にできる
例えば、Sortというinterfaceを提供しつつ、
具体的にどういうアルゴリズムでsortするかを切り替えるとか
入力を見てから、相性の良いアルゴリズムを選択するなど
構成要素
Strategy Interface
Concrete Strategy
Strategy Interfaceを実装する
Context
Strategyを切り替える
code:mermaid
classDiagram
Context --> Strategy : Uses
Strategy <|-- ConcreteStrategyA
Strategy <|-- ConcreteStrategyB
Strategy <|-- ConcreteStrategyC
class Context {
+strategy: Strategy
+execute(): void
}
class Strategy {
+execute(): void
}
class ConcreteStrategyA {
+execute(): void
}
class ConcreteStrategyB {
+execute(): void
}
class ConcreteStrategyC {
+execute(): void
}
単純な例
まず、Strategyパターンでないものを見てみる
Strategyが埋め込まれてしまっている例
code:ts
function calcPrice(order, type) {
if (type === "normal") {
return order.amount * 1.1
}
if (type === "vip") {
return order.amount * 0.9
}
if (type === "campaign") {
return order.amount * 0.8
}
}
calcPriceは、具体的なtypeの値を知っており、それに伴って行う処理の内容も知っている
「what = 価格を出す」と「how = type」が1つの関数に密結合している
typeの種類が増えるたびにこの関数を修正しないといけない
Strategyパターン化する
code:ts
type PricingStrategy = (order) => number
function calcuPrice(order, strategy: PricingStrategy) {
return strategy(order)
}
code:ts
const normalPricing = (order) => order.amount * 1.1
const vipPricing = (order) => order.amount * 0.9
const campaignPricing = (order) => order.amount * 0.8
calcuPriceは、どう計算するか(how)を一切知らない
PricingStrategyを満たしさえすれば、利用者が自由にstrategyを与えられる
もう少し実践的な例
例えば下記のように使う
code:ts
Effect.retry(task, Schedule.fixed("100 millis")) // 100msごとにretryされる
Effect.retry(task, Schedule.exponential("10 millis")) // exponential backooffされる
Effect.retryというContextに、Schedule.なんちゃらというStrategyを与えることで、それに沿ってリトライされる
Effect.retryが内部で「fixedが来たらこうする」みたいな具体的な実装をしているわけでないのがポイント
hs