generated at
AbstractFactoryパターン

原典にあたっていなく、その辺の記事を読んだだけなので解釈がおかしいかもしれないmrsekut



ここに書いてたやつを雑にtsに書き直した
細かいところはかなり適当。パッとわかればokmrsekut
動かしてないので普通に間違っている可能性もある
ts
class Client { main(args) { const hotPot = new HotPot(new Pot()); const factory = this.createFactory(args[0]); hotPot.addSoup(factory.getSoup()); hotPot.addMain(factory.getMain()); hotPot.addVegetables(factory.getVegetables()); hotPot.addOtherIngredients(factory.getOtherIngredients()); } createFactory(type) { switch (type) { case "kimuchi": return new KimuchiFactory(); case "sukiyaki": return new SukiyakiFactory(); default: return new MizutakiFactory(); } } } class MizutakiFactory { getSoup() { return new ChickenBonesSoup(); } getMain(): Protein { return new Chicken(); } getVegetables(): Vegetable[] { const vegetables = new ArrayList<Vegetable>(); vegetables.add(new ChineseCabbage()); vegetables.add(new Leek()); vegetables.add(new Chrysanthemum()); return vegetables; } getOtherIngredients(): Ingredients[] { const otherIngredients = new ArrayList<Ingredients>(); otherIngredients.add(new Tofu()); return otherIngredients; } }


抽象化せずに登場人物を列挙すると以下のようになる
HotPot
「鍋」というclass
鍋と言っても複数種類の鍋があるので、どの鍋を作るのか?という話をしている
スープや具材に選択肢がありすぎるので、「水炊き鍋を作りたい」と言えど、想定されていない具材がツッコまれる可能性がある
この選択肢を揃えるために、これに対するFactoryを用意する
createFactory
適切な鍋を作る専用のFactory
typeさえ指定すれば、想定された鍋用のfactoryが返ってくる
これって、Clientの内部に定義するの微妙じゃない #??
いくつかの選択肢
具体的な調理法を想定した各種鍋を生成する用の個別的なFactory
Kimmuchi
Sukiyaki
Mizutaki
Client
鍋の利用者


以下のような暗黙な前提がありそうな気がする
「完全constructorではない」ことは前提されているっぽい
ちゃんと書くと、上の例における「HotPotに完全constructorが定義されていない」
実際、書こうと思ってもかなり難しい気はするmrsekut
setterの存在も前提されている
ちゃんと書くと、上の例における「HotPotの各種propertyにsetterが存在する」
完全constructorの延長の話だが、instantiateされたあとにデータが入ることが前提されいてる
この意味で、このパターンを使用する前段階でアンチパターンを踏んでいる気はするmrsekut
というよりも、このアンチパターンを踏み抜いた後の妥協策としてのAbstract Factoryパターンだったりするんだろうか?





『Clean Code』 p.68~の1例
悪い例
ts
class Hoge { calculatePay(e: Employee): Money { switch (e.type) { case "COMMISSIONED": return calculateCommissionedPay(e); case "HOURLY": return calculateHourlyPay(e); case "COMMISSIONED": return calculateCommissionedPay(e); default: throw new Error(e.type); } } isPayday(e: Employee) { switch (e.type) {..} } deliverPay(e: Employee, pay: Money) { switch (e.type) {..} } }
各methodが単一責任でない
swtichを使ってtypeごとに、別のmethodを呼んでいる
同じようなswitch文が各methodごとにある
今後、typeが1つ増えたら、全てのmethodを修正する必要がある
良い例
swtich文を1箇所に配置する
それがFactory
typeによって、異なるclassを返すようにする
ts
abstract class Employee { abstract calculatePay(): Money; abstract isPayday(): boolean; abstract deliverPay(pay: Money): void; } class CommissionedEmployee extends Employee {..} class HourlyEmployee extends Employee {..} class SalariedEmployee extends Employee {..} // Factory class EmployeeFactoryImpl { makeEmployee(r: EmployeeRecord): Employee { switch (r.type) { case "COMMISSIONED": return new CommissionedEmployee(r); case "HOURLY": return new HourlyEmployee(r); case "SALARIED": return new SalariedEmployee(r); default: throw new InvalidEmployeeType(r.type); } } }