blog

JSのデザインパターンを理解する - ファクトリーパターン

前の記事で、設計原則 - 実装のためのプログラミングではなく、インターフェースのためのプログラミング - について触れました。確かに、インスタンス化はしばしば「結合」の問題を引き起こす活動なので、イン...

Jan 2, 2021 · 4 min. read
シェア

序文

前回の記事で述べた設計原則のひとつに、オブジェクトをインスタンス化するときは、実装をプログラミングするのではないというものがあります。確かに、インスタンス化はしばしば「結合」の問題を引き起こす行為なので、常にオープンに行うべきではありません。では、どうすればもっと疎結合に開発できるのでしょうか?それがこの記事のテーマであるファクトリーパターンです。

シンプルなファクトリーパターン

ピザの注文システムを作るとすると、コードは次のようになります。

function orderPizza () {
 let pizza = new Pizza();
 pizza.prepare();
 pizza.bake();
 pizza.cut();
 pizza.box();
 return pizza;
}

しかし、ピザの種類が増えると、どのピザを作るかを判断するために、あるコードを追加する必要があります。

function orderPizza (type) {
 let pizza;
 if (type === 'a') {
 pizza = new aPizza(); 
 } else if(type === 'b') {
 pizza = new bPizza(); 
 ) else {
 pizza = new cPizza(); 
 }
 ...
}

しかし、現実にはピザの種類はそれほど多くありませんし、時代の変化とともにその種類も削除される可能性があります。そのため、前述の設計原則(クラスは拡張に対してオープンであり、設計に対してはクローズであるべき)に従って、変更される部分と変更されない部分をカプセル化する必要があります。

class PizzaStore {
 factory = null;
 constructor (factory) {
 this.factory = factory;
 }
 orderPizza (type) {
 let pizza = this.factory.createPizza(type);
 pizza.prepare();
 pizza.bake();
 pizza.cut();
 pizza.box();
 return pizza;
 }
}
class SimplePizzaFactory {
 createPizza (type) {
 let pizza;
 if (type === 'a') {
 pizza = new aPizza(); 
 } else if(type === 'b') {
 pizza = new bPizza(); 
 ) else {
 pizza = new cPizza(); 
 }
 return pizza;
 }
}

ピザの生成ロジックを方程式から取り除くことで、オブジェクトの生成に特化したメソッドを得ることができます。ファクトリーを使うことで、orderPizzaはオブジェクトを生成する具体的なプロセスは気にせず、ただピザを返してくれればいいのです。こうすることで他にどんな利点があるでしょうか?まず第一に、ファクトリーメソッドは、オブジェクトを生成する必要性がファクトリーを使用することができる限り、orderPizzaメソッドで使用することができるだけでなく、再利用可能であり、時間を変更するだけでファクトリーを変更する必要があります。

上記のメソッドは、実際には単純なファクトリと呼ばれ、実際には、単純なファクトリは、デザインパターンではなく、プログラミングの習慣のようなものですが、このメソッドが頻繁に使用されるため、一部の開発者は、ファクトリパターンを誤解しています。しかし、私はそれがパターンに属していないが、行に十分な、時間の開発は常に、実際には、唯一の "完璧な "の一定程度を必要とするデザインパターンの様々な適用する必要はありませんと言わなければならない十分です。

では、本物のファクトリーパターンはどうですか?

工場モデル

ファクトリー・メソッド・パターンは、オブジェクトを生成するインターフェースを定義しますが、どのクラスをインスタンス化するかはサブクラスに任せます。ファクトリーメソッドによって、クラスはインスタンス化をサブクラスに委ねることができます。

ピザがいろいろな地域で食べられるようになり、いろいろな味が出るようになったとしましょう。では、上記のシンプルなファクトリーパターンを使ったらどうでしょうか?まずSimplePizzaFactoryを継承してcreatePizzaメソッドをオーバーライドする3つの異なるファクトリー、aFactory, bFactory, cFactoryを書きます。

aStore = new PizzaStore(new aFactory());
aStore.orderPizza('a');
bStore = new PizzaStore(new bFactory());
bStore.orderPizza('b');

このような書き方でも問題はなさそうですが、誰かがあなたが設定したPizzaStoreを使わず、自作のプロセスを使い始めるかもしれません。そのため、より耐性のあるフレームワークを構築する必要があり、ここで今回話題になったパターン、Factoryパターンが登場します。

PizzaStoreにcreatePizzaメソッドを組み込みますが、"abstractメソッド "として設定し、異なるアウトレット用にサブクラスを作成します。PizzaStoreの変更を見てみましょう。

class PizzaStore {
 orderPizza(type){
 let pizza;
 pizza = this.createPizza(type);
 pizza.prepare();
 pizza.bake();
 pizza.cut();
 pizza.box();
 return pizza;
 }
 creatPizza () {} // 抽象メソッド
}

これで、PizzaStoreのサブクラスは独自のcreatePizzaメソッドを定義する責任を持つようになり、柔軟な実装を実現できるようになりました。まずはブランチのコードを実装してみましょう。

class aPizzaStore extends PizzaStore {
 creatPizza (type) {
 let pizza;
 if (type === 'a') {
 pizza = new aPizza(); 
 } else if(type === 'b') {
 pizza = new bPizza(); 
 ) else {
 pizza = new cPizza(); 
 }
 return pizza;
 }
}

orderPizzaの中のピザが、どの具象クラスが関与しているかを知ることなく、様々なメソッドを実行していることがわかります。

概要

ファクトリーメソッドパターンは型固有のインスタンス化をカプセル化するもので、 pizzaStore がオブジェクトを生成するために提供する createPizza メソッドも "ファクトリーメソッド" と呼ばれます。pizzaStore クラスにはこのメソッドを使用するメソッドが他にもあるかもしれませんが、実際にこのメソッドを実装してオブジェクトをインスタンス化するのはサブクラスのみです。

これを見ても、シンプルファクトリーとファクトリーパターンの違いについて混乱する人もいるはずです。確かに似てはいますが、シンプルファクトリーは全体を一箇所にまとめて最終的な解決を図る方法です。しかし、ファクトリーパターンはフレームワークを設定し、サブクラスに実装を決定させる方法です。しかし、単純なファクトリーは生成される製品を変更することができないので、単純なファクトリーにはファクトリーメソッドのような柔軟性がありません。

Read next

GitHub Actions を使って Hexo ブログを GitHub Pages に自動デプロイする。

最近、継続的インテグレーションや継続的デプロイのために GitHub Actions を使っている人をよく見かけるようになりました。そこで、ふと思い立って私も GitHub Actions を使って個人ブログのデプロイを自動化してみました。GitHub Actions は本当にいい匂いがします! Hexo ブログをデプロイすると、まず...

Jan 1, 2021 · 4 min read