複数のレシピを元に計算する
formatterに、複数のルールを適用して、最終的な整形を行う
1つの反物を、カーテン、タッセル、フリルのようにいくつかの用途に使う
窓のサイズから完成物の高さを計算する際に、いくつかの補正ルールを適用する
いくつかの注文内容から、ルールに従って最終的な加工代を算出する
こういったものはいずれも、
最終的に求めたい値であるSと、
それを計算するためのレシピ集Eの集合で
連鎖的に求められる
2つの方針が考えられる
他にももっとありそう
reducerのように、stateとeventとみなす
例えばtextや高さをstateとみなして、個々のレシピをeventとみなす
[e1, e2, e3, e4].reduce(reducer, initState)のようにして個々のpluginを使える
個々のレシピをinput/outputが同じ構造の関数として表現する
e = e4 . e3 . e2 . e1のように関数を合成して、それにsを適用することで結果が得られる
利点
個々のレシピの足し引きがし易い
Eを束ねているところに1つ追加削除するだけ
code:ts
結合が小さくなる
元の処理が具体的なレシピの知識を知らずに済む
例えば、上記のアプローチをしない場合、calcSizeのような関数はtasselなどの具体的な要素を引数に取る必要がある
が、このアプローチをすればRecipe型というinterfaceにのみ依存すれば済む
その関数の呼び出し元かどこかでレシピを構成することになるが、そこで具体的なものに依存すれば良い
外部との境界付近で作れば良い
code:ts
const applyFormat = (text, rules) => rules.reduce((acc, func) => func(acc), text);
凝集性が上がる
tasselに関するルールは、tassel package内に入れれば良い
sizepackageかtasselpackageのいずれに入れるかというトレードオフを取っている
関数の型を見れば、補正用のものだなと言うのが瞬時にわかる
code:ts
// keywordFormat.js(仮想のモジュールファイル)
export const emphasizeKeywords: Rule = (text) => text.replace(/重要/g, "**重要**");
// newLineFormat.js
export const addNewLines: Rule = (text) => text.replace(/。/g, "。\n");
// main.js
import { emphasizeKeywords } from './keywordFormat';
import { addNewLines } from './newLineFormat';
問題点
順序に依存する処理がありうる
例えば、補正の例で考えると、height -> 更新したheightのような型のレシピを作るが、
乗除が含まれるような物がある場合、レシピ構成時の順序が回答に影響してしまう
レシピ内での条件が複雑な場合に読みづらくなる
code:ts
type A = 'a1' | 'a2'
type B = 'b1' | 'b2'
type C = 'c1' | 'c2'
のような構造があるときに、
a1のとき、b1|b2ごとに別の処理をし、
a1のとき、b1|b2とc1|c2ごと(4パターン)で別の処理をする
のような、2 + 4 = 6パターンで別の結果になるような補正があるとき、
この6個をフラットに並べるより、構造化して書かれている方が読みやすい
どこでレシピ集を構成するかという話が出てくる
上記でいうと、[e1,e2,e3,e4]といった配列をどこで定義するのかという話である
特に、[e1(a), e2(b,c), e3(c), e4(d)]のように、個々のレシピの構成自体に引数が必要なときに問題になる
レシピ集を構成する場所が散らばるのは良くない
いくつかの場所でreducerを呼び出したいときに、その場所ごとにe[]を構成するのは良くないということ
レシピの追加漏れがありうる
レシピの追加があるごとに、それら全部をに1行追加する必要が出てくる
2つの方針
大きなobject → レシピ集という関数を1つ用意する
これを外部との境界で呼べば良い
対象の関数の中で構成する
レシピ集の構成と適用を同じ場所で行う
しかしそうすると、対象の関数が結局具体的な要素に依存してしまう
code:ts
const calcHeight = (h: Height, t: Tassel)=> {
const rules = [
// 引数を使ってルールを作成
];
return rules.reduce((acc, rule) => rule(acc), h);
};