コンストラクターvsファクトリーメソッド[終了]


181

クラスをモデル化するとき、初期化の好ましい方法は何ですか?

  1. コンストラクタ、または
  2. ファクトリメソッド

そして、それらのいずれかを使用するための考慮事項は何でしょうか?

特定の状況では、オブジェクトを構築できない場合にnullを返すファクトリメソッドを使用することを好みます。これにより、コードがきれいになります。コンストラクターから例外をスローするのとは対照的に、代替アクションを実行する前に、戻り値がnullでないかどうかを簡単に確認できます。(私は個人的に例外は好きではありません)

たとえば、id値を期待するクラスにコンストラクターがあるとします。コンストラクターはこの値を使用して、データベースからクラスを生成します。指定されたIDのレコードが存在しない場合、コンストラクターはRecordNotFoundExceptionをスローします。この場合、そのようなすべてのクラスの構築をtry..catchブロックで囲む必要があります。

これとは対照的に、レコードが見つからない場合にnullを返すこれらのクラスに静的ファクトリメソッドを設定できます。

この場合、コンストラクタとファクトリメソッドのどちらのアプローチが優れていますか?

回答:


66

デザインパターンの 108ページから:Gamma、Helm、Johnson、Vlissidesによる再利用可能なオブジェクト指向ソフトウェアの要素。

ファクトリメソッドパターンを使用する場合

  • クラスは、作成する必要があるオブジェクトのクラスを予測できません
  • クラスは、サブクラスが作成するオブジェクトを指定することを望んでいます
  • クラスはいくつかのヘルパーサブクラスの1つに責任を委任し、どのヘルパーサブクラスがデリゲートであるかという知識をローカライズしたい

21
静的ファクトリメソッドは、GoFデザインパターン-ファクトリメソッドパターンとは異なります。 stackoverflow.com/questions/929021/...
SREEラマ

GoF設計パターンのFactoryメソッドパターンと比較しないでください。
スリーラマ

137
これは私に何も説明しません
Sushant 2014

@Sushant、なぜそうなのですか?
PaulD 2014年

2
この答えは質問に答えるものではなく、概念を理解/説明するために読む情報を中継するだけです...これはコメントです。
Crt

202

それらが何であるか、そしてなぜ私たちはそれらを持っているのかを自問してください。どちらもオブジェクトのインスタンスを作成するためにあります。

ElementarySchool school = new ElementarySchool();
ElementarySchool school = SchoolFactory.Construct(); // new ElementarySchool() inside

これまでのところ違いはありません。ここで、さまざまな学校タイプがあり、ElementarySchoolの使用からHighSchool(ElementarySchoolから派生するか、またはElementarySchoolと同じインターフェースISchoolを実装する)に切り替えたいとします。コードの変更は次のようになります。

HighSchool school = new HighSchool();
HighSchool school = SchoolFactory.Construct(); // new HighSchool() inside

インターフェースの場合、次のようになります。

ISchool school = new HighSchool();
ISchool school = SchoolFactory.Construct(); // new HighSchool() inside

このコードが複数の場所にある場合、ファクトリメソッドを変更すると完了しているため(インターフェイスで2番目の例を使用している場合)、ファクトリメソッドの使用はかなり安価であることがわかります。

そして、これが主な違いと利点です。複雑なクラス階層の処理を開始し、そのような階層からクラスのインスタンスを動的に作成する場合は、次のコードを取得します。次に、ファクトリメソッドは、インスタンス化する具体的なインスタンスをメソッドに通知するパラメータを取得します。MyStudentクラスがあり、対応するISchoolオブジェクトをインスタンス化して、生徒がその学校のメンバーになるようにするとします。

ISchool school = SchoolFactory.ConstructForStudent(myStudent);

これで、さまざまなIStudentオブジェクトに対してインスタンス化するISchoolオブジェクトを決定するビジネスロジックを含む1つの場所がアプリにあります。

したがって、単純なクラス(値オブジェクトなど)の場合はコンストラクターで十分です(アプリケーションをオーバーエンジニアリングしたくない)が、複雑なクラス階層の場合はファクトリーメソッドを使用することをお勧めします。

このようにして、4冊の本「プログラムをインターフェイスではなく実装にする」の最初の設計原則に従います。


2
単純なクラスだと思っていても、誰かがあなたの単純なクラスを拡張する必要がある可能性があるので、ファクトリーメソッドの方が優れています。たとえば、ElementarySchoolから始めても、後で誰か(自分を含む)がPrivateElementarySchoolとPublicElementarySchoolで拡張する場合があります。
ジャック

10
これは受け入れられる答えでなければなりません
am05mhz

2
@David、良い答えですが、各インターフェイスの実装が構築に異なるパラメーターを必要とする可能性がある例を拡張できますか?これはばかげた例です:IFood sandwich = new Sandwich(Cheese chz, Meat meat);そして、IFood soup = new Soup(Broth broth, Vegetable veg);工場やビルダーはどのようにここで助けることができますか?
ブライアン、

1
工場での使用の目的に関する他の3つの説明を読んだところ、これがようやく「クリック」してくれました。ありがとうございました!
Daniel Peirano 2017

なぜこれは受け入れられない答えですか?
トーマス

74

有効なJava 2の 項目(アクセスできる場合)を読む必要があります。1:コンストラクターの代わりに静的ファクトリーメソッドを検討します

静的ファクトリメソッドの利点:

  1. 彼らは名前を持っています。
  2. 呼び出されるたびに新しいオブジェクトを作成する必要はありません。
  3. それらは、戻り型の任意のサブタイプのオブジェクトを戻すことができます。
  4. これらは、パラメーター化された型インスタンスを作成する冗長性を減らします。

静的ファクトリメソッドの欠点:

  1. 静的ファクトリーメソッドのみを提供する場合、パブリックコンストラクターまたは保護コンストラクターを持たないクラスはサブクラス化できません。
  2. 他の静的メソッドと簡単に区別できない

4
これは、Javaの重大なバグではなく、一般的なOOD問題のようです。そこさえしていない、多くのオブジェクト指向言語です持っているのコンストラクタは、まだサブクラス化はうまく動作します。
イェルクWミッターク

1
@cherouvimなぜほとんどの場合、コンストラクターを使用してコードが記述されるのか( factory methods are better than Constructors. ( Item-1 ) ) Effective java
Asif Mushtaq

良い点。ただし、Java固有です。ファクトリメソッドを他の静的メソッドと区別できるようにする言語機能のケースを作成できます。
OCDev

30

コンストラクターは、理解と記述がより簡単であるため、デフォルトで推奨されます。ただし、クライアントコードで理解されているように、オブジェクトの構造の美しさをその意味上の意味から切り離す必要がある場合は、ファクトリを使用した方がよいでしょう。

コンストラクタとファクトリの違いは、たとえば、変数と変数へのポインタに似ています。間接性には別のレベルがありますが、これは欠点です。しかし、もう1つのレベルの柔軟性もあり、これは利点です。したがって、選択を行う際には、このコスト対利益の分析を行うことをお勧めします。


17
したがって、(TDDスタイル)ジョブを実行する最も簡単な方法として、コンストラクターから始めます。そして、コードの臭いがし始めたら、ファクトリーにリファクタリングします(呼び出すコンストラクターを決定する条件付きロジックの繰り返しなど)?
AndyM 2010年

1
非常に重要なポイント。工場やコンストラクタを比較し、ユーザの研究では、工場はAPIの使いやすさに有害であることを示す非常に重要な結果が見つかりました:「ユーザーが大幅に多くの時間を必要とした(p = 0.005)コンストラクタよりも工場を持つオブジェクトを構築するための」APIデザインで[ファクトリパターン:ユーザビリティ評価 ]。
mdeff

12

ファクトリーは、オブジェクトの作成で追加の制御が必要な場合にのみ、コンストラクターでは実行できない方法で使用してください。

たとえば、ファクトリはキャッシュする可能性があります。

ファクトリを使用するもう1つの方法は、構築するタイプがわからない場合です。プラグインファクトリシナリオでこのタイプの使用法がよく見られます。各プラグインは、ベースクラスから派生するか、なんらかの種類のインターフェイスを実装する必要があります。ファクトリーは、ベースクラスから派生するか、インターフェースを実装するクラスのインスタンスを作成します。


11

「Effective Java」の第2版の引用、項目1:コンストラクターの代わりに静的ファクトリーメソッドを検討する(p。5:

静的ファクトリーメソッドは、デザインパターンのファクトリーメソッドパターン [Gamma95、p。107] と同じではないことに注意しください。このアイテムで説明されている静的ファクトリーメソッドには、デザインパターンに直接対応するものはありません。」


10

「効果的なJava」に加えて(別の回答で述べられているように)、別の古典的な本も示唆しています:

オーバーロードされたコンストラクタよりも、静的なファクトリメソッド(引数を説明する名前を持つ)を優先します。

例えば。書かないで

Complex complex = new Complex(23.0);

代わりに書く

Complex complex = Complex.fromRealNumber(23.0);

この本では、Complex(float)コンストラクターをプライベートにして、ユーザーに静的ファクトリーメソッドを呼び出させるようにしています。


2
本のその部分を読んで私をここに連れてきました
パープルヘイズ

1
@Bayrem:私も最近読んでいるので、回答に追加する必要があると思いました。
blue_note 2018年

1
関連ノートで、あなたはいくつかの有用見つけるかもしれない命名規則がで働いjava.timeのフレームワークの命名についてはfrom…to…parse…with…、など。覚えておいてくださいjava.timeのクラスは不変であることを内蔵しているが、これらの命名規則の一部はあまりにも可変クラスに従うことが役に立つかもしれません。
バジルブルク

7

CAD / CAMアプリケーションの具体例。

カットパスは、コンストラクターを使用して作成されます。カットするパスを定義する一連の線と円弧です。一連の線と弧は異なる場合があり、座標が異なる場合がありますが、リストをコンストラクタに渡すことで簡単に処理できます。

ファクトリーを使って形を作ります。シェイプクラスがあるので、各シェイプは、どのタイプのシェイプであるかに応じて異なる設定になるためです。ユーザーが選択するまで、初期化する形状はわかりません。


5

たとえば、id値を期待するクラスにコンストラクターがあるとします。コンストラクターはこの値を使用して、データベースからクラスを生成します。

このプロセスは、間違いなくコンストラクタの外にある必要があります。

  1. コンストラクターはデータベースにアクセスしないでください。

  2. タスクとコンストラクタ理由はすることですデータメンバを初期化するとしたクラス不変条件を確立し、コンストラクタに渡された値を使用して。

  3. それ以外の場合は、静的ファクトリーメソッドを使用するか、より複雑なケースでは別個のファクトリーまたはビルダークラスを使用するのがより良いアプローチです。

Microsoftからのいくつかのコンストラクタガイドライン

コンストラクタで最小限の作業を行います。コンストラクターは、コンストラクターパラメーターをキャプチャする以外に多くの作業を行うべきではありません。その他の処理のコストは、必要になるまで延期する必要があります。

そして

目的の操作のセマンティクスが新しいインスタンスの構築に直接マッピングされない場合は、コンストラクターの代わりに静的ファクトリーメソッドを使用することを検討してください。


2

オブジェクトの作成中に、いくつかの値/条件を確認/計算する必要がある場合があります。そして、それが例外をスローする可能性がある場合-コンストラクタは非常に悪い方法です。だからあなたはこのようなことをする必要があります:

var value = new Instance(1, 2).init()
public function init() {
    try {
        doSome()
    }
    catch (e) {
        soAnotherSome()
    }
}

追加の計算はすべてinit()で行われます。しかし、本当にこのinit()について知っているのは開発者としてのあなただけです。そしてもちろん、数か月後にはそれを忘れてしまいます。しかし、ファクトリーがある場合-このinit()を直接呼び出しから隠して、必要なすべてを1つのメソッドで実行するだけなので、問題ありません。このアプローチでは、作成の失敗やメモリリークの問題はありません。

誰かがキャッシングについてあなたに言いました。それは良いです。ただし、ファクトリーウェイで使用するのに適したFlyweightパターンについても覚えておく必要があります。

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