web-dev-qa-db-ja.com

コンストラクターとファクトリーメソッド

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

  1. コンストラクター、または
  2. 工場メソッド

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

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

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

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

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

167
Hemanshu Bhojak

Design Patterns:Gamma、Helm、Johnson、およびVlissidesによる再利用可能なオブジェクト指向ソフトウェアの要素の108ページから。

次の場合にFactory Methodパターンを使用します

  • クラスは、作成しなければならないオブジェクトのクラスを予測できません
  • クラスは、サブクラスが作成するオブジェクトを指定することを望んでいます
  • クラスはいくつかのヘルパーサブクラスの1つに責任を委任し、どのヘルパーサブクラスが委任であるかに関する知識をローカライズしたい
63
Glenn

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

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つの場所ができました。

そのため、単純なクラス(値オブジェクトなど)の場合、コンストラクターは問題ありません(アプリケーションをオーバーエンジニアリングしたくない)が、複雑なクラス階層の場合はファクトリーメソッドが優先されます。

このようにして、 gang of four book "実装ではなく、インターフェイスへのプログラム"の最初の設計原則に従います。

186
David Pokluda

読む必要があります(アクセスできる場合) Effective Java 2Item 1:コンストラクターではなく静的ファクトリーメソッドを検討する

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

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

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

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

デフォルトでは、コンストラクタは理解および記述が簡単であるため、コンストラクタを優先する必要があります。ただし、クライアントコードが理解するセマンティックな意味からオブジェクトの構築の特性を明確に分離する必要がある場合は、ファクトリを使用することをお勧めします。

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

25

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

たとえば、工場にはキャッシングの可能性があります。

ファクトリを使用する別の方法は、構築したいタイプがわからないシナリオです。多くの場合、プラグインファクトリシナリオでこのタイプの使用法があります。各シナリオでは、各プラグインはベースクラスから派生するか、何らかのインターフェイスを実装する必要があります。ファクトリーは、ベースクラスから派生するか、インターフェースを実装するクラスのインスタンスを作成します。

11
Patrick Peters

「Effective Java」第2版、項目1からの引用:コンストラクターではなく、静的ファクトリーメソッドを検討してください。 5:

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

11
Eugen Labun

"effective Java"(別の回答で述べたように)に加えて、 別の古典的な本 も提案しています:

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

例えば。書かないで

_Complex complex = new Complex(23.0);
_

代わりに書く

_Complex complex = Complex.fromRealNumber(23.0);
_

この本はComplex(float)コンストラクターをprivateにして、ユーザーに静的ファクトリーメソッドを呼び出させることを提案しているだけです。

8
blue_note

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

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

形状は、工場を使用して作成されます。シェイプクラスが存在する間、各シェイプは、それがシェイプのタイプに応じて異なる方法でセットアップされるためです。ユーザーが選択するまで、どの形状を初期化するのかわかりません。

7
RS Conley

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

このプロセスは、必ずコンストラクターの外部にある必要があります。

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

  2. コンストラクターのタスクと理由は、データメンバーの初期化およびクラス不変式の確立コンストラクタに渡された値を使用します。

  3. 他のすべての場合、より良いアプローチは、static factory methodを使用するか、より複雑なケースでは個別のfactoryまたはbuilderクラス。

Microsoftの一部のコンストラクターガイドライン

コンストラクターで最小限の作業を行います。コンストラクターは、コンストラクターのパラメーターをキャプチャする以外の多くの作業を実行しないでください。他の処理のコストは、必要になるまで遅らせる必要があります。

そして

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

5
Lightman

オブジェクトの作成中に、いくつかの値/条件を確認/計算する必要がある場合があります。そして、例外をスローできる場合、constructroは非常に悪い方法です。したがって、次のようなことをする必要があります。

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

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

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

1
trashgenerator