コンストラクター関数とファクトリー関数


150

誰かがJavaScriptのコンストラクタ関数とファクトリ関数の違いを明確にできますか?

どちらを代わりに使用するのですか?

回答:


149

基本的な違いは、コンストラクター関数がnewキーワードと共に使用されることです(これにより、JavaScriptは自動的に新しいオブジェクトを作成thisし、関数内でそのオブジェクトに設定して、オブジェクトを返します)。

var objFromConstructor = new ConstructorFunction();

ファクトリ関数は「通常の」関数のように呼び出されます。

var objFromFactory = factoryFunction();

ただし、「ファクトリ」と見なされるには、オブジェクトの新しいインスタンスを返す必要があります。ブール値などを返しただけの場合は、「ファクトリ」関数と呼ばないでください。これは、のように自動的に行われるわけではありませんがnew、場合によっては柔軟性が向上します。

非常に単純な例では、上記で参照した関数は次のようになります。

function ConstructorFunction() {
   this.someProp1 = "1";
   this.someProp2 = "2";
}
ConstructorFunction.prototype.someMethod = function() { /* whatever */ };

function factoryFunction() {
   var obj = {
      someProp1 : "1",
      someProp2 : "2",
      someMethod: function() { /* whatever */ }
   };
   // other code to manipulate obj in some way here
   return obj;
}

もちろん、ファクトリー関数をその単純な例よりもはるかに複雑にすることができます。

ファクトリー関数の利点の1つは、返されるオブジェクトが、パラメーターに応じていくつかの異なるタイプになる可能性がある場合です。


14
「(編集:これは問題になる可能性があります。新しい機能がなくても機能は実行されますが、期待どおりには動作しないためです。」これは、「new」でファクトリー関数を呼び出そうとした場合、または「this」キーワードを使用してインスタンスに割り当てようとした場合にのみ問題になります。それ以外の場合は、単に新しい任意のオブジェクトを作成し、それを返します。問題はありません。ボイラープレートが少なく、APIにインスタンス化の詳細を漏らすことなく、異なる、より柔軟な方法を実行するだけです。
エリックエリオット

6
両方のケース(コンストラクター関数とファクトリー関数)の例は一貫している必要があることを指摘したかったのです。ファクトリー関数の例には、ファクトリーsomeMethodによって返されたオブジェクトは含まれていません。そのため、少しぼやけてしまいます。ファクトリ関数の内部では、1つだけを実行するとvar obj = { ... , someMethod: function() {}, ... }、返される各オブジェクトが異なるコピーを保持することになり、そのコピーはsomeMethod望ましくない場合があります。これは、ファクトリ関数の使用newprototype内部で役立ちます。
Bharat Khatri 2014年

3
既に述べたようnewに、コンストラクター関数での使用を忘れるバグを残したくないという理由だけでファクトリー関数を使用しようとする人もいます。コンストラクターをファクトリー関数のに置き換える方法を確認する必要があるのはそのときだと思いました。そこで、例の一貫性が必要だと思いました。とにかく、答えは十分に有益です。これは私が提起したかった点であり、私が答えの質を何らかの方法で引き下げているわけではありません。
Bharat Khatri 2014年

4
私にとって、Factory関数の最大の利点は、いくつかのアプリケーションで役立つかもしれない、より良いカプセル化データ非表示を取得できることです。すべてのインスタンスプロパティとメソッドをパブリックにしてユーザーが簡単に変更できるようにしても問題がなければ、「新しい」キーワードを嫌う人がいない限り、コンストラクタ関数の方が適していると思います。
devius 14

1
@Federico-ファクトリーメソッドは単純なオブジェクトだけを返す必要はありません。new内部的に使用Object.create()することも、特定のプロトタイプでオブジェクトを作成するために使用することもできます。
nnnnnn

109

コンストラクターを使用する利点

  • ほとんどの本はコンストラクタを使用することを教え、 new

  • this 新しいオブジェクトを参照します

  • 一部の人々は方法がvar myFoo = new Foo();読むのが好きです。

欠点

  • インスタンス化の詳細が(new要件を介して)呼び出し元のAPIにリークされるため、すべての呼び出し元はコンストラクターの実装に密接に結合されます。ファクトリーの追加の柔軟性が必要な場合は、すべての呼び出し元をリファクタリングする必要があります(確かに、ルールではなく例外的なケースです)。

  • 忘却newはよくあるバグですif (!(this instanceof Foo)) { return new Foo() }。コンストラクタが正しく呼び出されるように、ボイラープレートチェックを追加することを強く検討してください()。編集:ES6(ES2015)以降newclassコンストラクターを忘れることができません。

  • instanceofチェックを行うと、new必要かどうかが曖昧になります。私の意見では、そうであってはなりません。new要件を効果的に短絡しました。つまり、欠点#1をなくすことができます。しかし、名前を除いて、ファクトリー関数が追加されただけであり、定型文、大文字、柔軟性の低いthisコンテキストが追加されています。

コンストラクタはオープン/クローズの原則を破る

しかし、私の主な懸念は、それがオープン/クローズドの原則に違反していることです。コンストラクターのエクスポートを開始し、ユーザーがコンストラクターの使用を開始してから、代わりにファクトリの柔軟性が必要であることに気づきます(たとえば、実装を切り替えてオブジェクトプールを使用するか、実行コンテキスト間でインスタンス化するか、またはプロトタイプOOを使用すると、継承の柔軟性が向上します)。

しかし、行き詰まっています。でコンストラクターを呼び出すすべてのコードを壊さずに変更を行うことはできませんnew。たとえば、パフォーマンス向上のためにオブジェクトプールの使用に切り替えることはできません。

また、コンストラクターを使用すると、instanceof実行コンテキスト全体で機能せず、コンストラクターのプロトタイプがスワップアウトされた場合に機能しなくなります。またthis、コンストラクターから戻り始め、任意のオブジェクトのエクスポートに切り替えた場合も失敗します。これは、コンストラクターでファクトリーのような動作を有効にするために実行する必要があります。

工場を使用する利点

  • 少ないコード-ボイラープレートは必要ありません。

  • 任意のオブジェクトを返し、任意のプロトタイプを使用できるため、同じAPIを実装するさまざまなタイプのオブジェクトを作成する柔軟性が高まります。たとえば、HTML5プレーヤーとFlashプレーヤーの両方のインスタンスを作成できるメディアプレーヤー、またはDOMイベントやWebソケットイベントを発行できるイベントライブラリ。ファクトリは、実行コンテキスト全体でオブジェクトをインスタンス化し、オブジェクトプールを利用して、より柔軟なプロトタイプ継承モデルを可能にすることもできます。

  • ファクトリからコンストラクタに変換する必要がないため、リファクタリングが問題になることはありません。

  • の使用について曖昧さはありませんnew。しないでください。(それはthis悪い振る舞いをします、次のポイントを見てください)。

  • thisは通常どおりに動作します。したがって、これを使用して、他のメソッド呼び出しと同様に、親オブジェクトにアクセスできます(たとえば、内player.create()this参照playercallおよび期待どおりにをapply再割り当てしthisます。親オブジェクトにプロトタイプを保存すると、機能を動的にスワップアウトし、オブジェクトのインスタンス化に非常に柔軟なポリモーフィズムを有効にする優れた方法です。

  • 大文字にするかどうかについてのあいまいさはありません。しないでください。Lintツールは文句を言うでしょう、そしてあなたはを使おうとするように誘惑されnew、そしてあなたは上記の利点を取り消すでしょう。

  • 一部の人々は方法var myFoo = foo();またはvar myFoo = foo.create();読書が好きです。

欠点

  • new期待どおりに動作しません(上記を参照)。解決策:使用しないでください。

  • this新しいオブジェクトを参照しません(代わりに、コンストラクターがドット表記または角括弧表記で呼び出された場合(例:foo.bar())- thisfoo参照-他のすべてのJavaScriptメソッドと同じように-利点を参照してください)。


2
コンストラクタは、呼び出し側を実装に密結合させるという意味ですか?コンストラクター引数に関する限り、それらを使用して適切なコンストラクターを呼び出すには、これらをファクトリー関数に渡す必要があります。
Bharat Khatri 2014年

4
Open / Closedの違反について:これは依存関係の注入についてのすべてではありませんか?AがBを必要とする場合、Aがnew B()を呼び出すか、AがBFactory.create()を呼び出すか、どちらも結合を導入します。一方、コンポジションルートでAにBのインスタンスを与える場合、Aは、Bがどのようにインスタンス化されるかについてまったく何も知る必要はありません。建設業者にも工場にも用途があると感じています。コンストラクターは単純なインスタンス化用であり、ファクトリーはより複雑なインスタンス化用です。ただし、どちらの場合も、依存関係を注入するのが賢明です。
Stefan Billiet 2014年

1
DIは、状態、構成、ドメインオブジェクトなどの注入に適しています。それは他のすべてにとってはやり過ぎです。
エリックエリオット

1
大騒ぎは、要求newがオープン/クローズの原則に違反するということです。これらのコメントで許可されているよりもはるかに大きな議論については、medium.com / javascript-scene /…を参照してください。
エリックエリオット

3
どの関数もJavaScriptで新しいオブジェクトを返すことができ、その多くはnewキーワードなしで返すので、newキーワードが実際に追加の読みやすさを提供するとは思いません。IMO、発信者がさらに入力できるようにするために、フープをジャンプするのはばかげているようです。
エリックエリオット

39

コンストラクターは、呼び出したクラスのインスタンスを返します。ファクトリ関数は何でも返すことができます。任意の値を返す必要がある場合、またはクラスに大きなセットアッププロセスがある場合は、ファクトリ関数を使用します。


5

コンストラクター関数の例

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");
  • newプロトタイプ化されたオブジェクトを作成し、作成したオブジェクトを値としてUser.prototype呼び出します。Userthis

  • new オペランドの引数式をオプションとして扱います。

         let user = new User;

    引き起こすnew呼び出すためにUser、引数なしで。

  • newコンストラクタがオブジェクトの値を返し、それが代わりに返さない限り、作成したオブジェクトを返します。これは、ほとんどの場合無視できるエッジケースです。

長所と短所

コンストラクター関数によって作成されたオブジェクトは、コンストラクターのprototypeプロパティからプロパティを継承instanceOfし、コンストラクター関数の演算子を使用してtrueを返します。

上記の動作は、コンストラクタprototypeを既に使用した後でコンストラクタのプロパティの値を動的に変更すると失敗する可能性があります。そうすることはまれであり、コンストラクターがclassキーワードを使用して作成された場合は変更できません。

extendsキーワードを使用して、コンストラクター関数を拡張できます。

コンストラクター関数はnullエラー値として返すことはできません。これはオブジェクトデータタイプではないため、によって無視されnewます。

Factory関数の例

function User(name, age) {
  return {
    name,
    age,
  }
};

let user = User("Tom", 23);

ここでは、ファクトリ関数はなしで呼び出されnewます。関数は、その引数とそれが返すオブジェクトのタイプである場合、直接または間接的な使用に対して完全に責任があります。この例では、引数から設定されたいくつかのプロパティを持つ単純な[オブジェクトオブジェクト]を返します。

長所と短所

オブジェクト作成の実装の複雑さを呼び出し元から簡単に隠します。これは、ブラウザのネイティブコード関数に特に役立ちます。

ファクトリ関数は常に同じタイプのオブジェクトを返す必要はなくnull、エラーインジケーターとして返すこともできます。

単純なケースでは、ファクトリ関数は構造と意味が単純な場合があります。

返されるオブジェクトは通常、ファクトリ関数のprototypeプロパティを継承せず、から返さfalseinstanceOf factoryFunctionます。

extends拡張オブジェクトは、ファクトリ関数で使用されるコンストラクターprototypeprototypeプロパティからではなく、ファクトリ関数プロパティを継承するため、キーワードを使用してファクトリ関数を安全に拡張することはできません。


1
これは、に応じて掲載後半答えはこの質問、同じ被写体に
traktor53

「null」だけでなく、「new」も、contructor関数によって返されるすべての初期データ型を無視します。
Vishal

2

工場は「常に」優れています。オブジェクト指向言語を使用する場合

  1. 契約を決定します(方法とその内容)
  2. それらのメソッドを公開するインターフェースを作成します(JavaScriptにはインターフェースがないため、実装をチェックする何らかの方法を考え出す必要があります)
  3. 必要な各インターフェースの実装を返すファクトリを作成します。

実装(newで作成された実際のオブジェクト)は、ファクトリーユーザー/コンシューマーには公開されません。つまり、工場の開発者は、契約に違反しない限り、新しい実装を拡張して作成できます。また、工場の消費者は、コードを変更せずに新しいAPIを利用できます。彼らが新しいものを使用し、「新しい」実装が登場した場合、「新しい」を使用するすべての行を変更して、「新しい」実装を使用する必要があります。工場では、コードは変更されません...

工場-何よりも優れている-春のフレームワークは、このアイデアを中心に完全に構​​築されています。


すべての行を変更する必要があるというこの問題を工場はどのように解決しますか?
コードネームJack

0

工場は抽象化の層であり、すべての抽象化と同様に、複雑さのコストがあります。ファクトリベースのAPIに遭遇した場合、指定されたAPIのファクトリが何かを理解することは、APIコンシューマーにとって困難な場合があります。コンストラクターの場合、発見は簡単です。

アクターとファクトリーの間で決定するとき、複雑さは利益によって正当化されるかどうか決定する必要があります。

これ以外の、または未定義の何かを返すことにより、Javascriptコンストラクタが任意のファクトリになる可能性があることに注意する価値があります。したがって、jsでは、両方の世界で最高の機能(発見可能なAPIとオブジェクトのプール/キャッシング)を利用できます。


5
JavaScriptでは、JSの関数は新しいオブジェクトを返すことができるため、コンストラクターを使用するコストは、ファクトリーを使用するコストよりも高くなります。コンストラクターnewthis、を必要とする、の動作を変更する、戻り値を変更する、プロトタイプ参照を接続する、有効にするinstanceof(これは嘘であり、この目的には使用しないでください)によって複雑さを追加します。表面的には、これらはすべて「機能」です。実際には、コードの品質に悪影響を及ぼします。
エリックエリオット

0

違いについては、エリックエリオットは非常によく説明しました、

しかし、2番目の質問については:

どちらを代わりに使用するのですか?

オブジェクト指向のバックグラウンドから来ている場合、コンストラクター関数はより自然に見えます。この方法では、newキーワードを使用することを忘れないでください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.