AbstractFactoryパターン
原典にあたっていなく、その辺の記事を読んだだけなので解釈がおかしいかもしれないmrsekut.icon
細かいところはかなり適当。パッとわかればokmrsekut.icon
動かしてないので普通に間違っている可能性もある
code:ts
class Client {
main(args) {
const hotPot = new HotPot(new Pot());
const factory = this.createFactory(args0); 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.icon
setterの存在も前提されている
ちゃんと書くと、上の例における「HotPotの各種propertyにsetterが存在する」
完全constructorの延長の話だが、instantiateされたあとにデータが入ることが前提されいてる
この意味で、このパターンを使用する前段階でアンチパターンを踏んでいる気はするmrsekut.icon
というよりも、このアンチパターンを踏み抜いた後の妥協策としてのAbstract Factoryパターンだったりするんだろうか?
『Clean Code』.icon p.68~の1例
悪い例
code: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を返すようにする
code: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);
}
}
}