質問
クラスをモデル化する場合、初期化の好ましい方法は何ですか:
- コンストラクタ、または
- ファクトリメソッド
そして、どちらを使用する場合の考慮事項は何ですか?
特定の状況では、オブジェクトを構築できない場合にnullを返すファクトリメソッドを使用することを好みます。これにより、コードがきれいになります。コンストラクターから例外をスローするのとは対照的に、代替アクションを実行する前に、戻り値がnullでないかどうかを確認するだけです。 (私は個人的に例外が嫌いです)
さて、id値を期待するクラスのコンストラクターがあります。コンストラクターはこの値を使用して、データベースからクラスを作成します。指定されたIDを持つレコードが存在しない場合、コンストラクターはRecordNotFoundExceptionをスローします。この場合、そのようなすべてのクラスの構築をtry..catchブロック内に含める必要があります。
これとは対照的に、レコードが見つからない場合はnullを返すクラスの静的ファクトリメソッドを使用できます。
この場合、コンストラクターまたはファクトリーメソッドのどちらのアプローチが良いですか?
解決
次の場合にFactory Methodパターンを使用します
- クラスは、作成する必要のあるオブジェクトのクラスを予測できません
- クラスは、サブクラスが作成するオブジェクトを指定することを望んでいます
- クラスはいくつかのヘルパーサブクラスの1つに責任を委任し、どのヘルパーサブクラスがデリゲートであるかに関する知識をローカライズしたい
他のヒント
それらが何であるか、そしてなぜそれらを持っているのかを自問してください。両方ともオブジェクトのインスタンスを作成するためにあります。
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つの場所ができました。
So-単純なクラス(値オブジェクトなど)の場合、コンストラクターは問題ありません(アプリケーションをオーバーエンジニアリングしたくない)が、複雑なクラス階層の場合はファクトリーメソッドが推奨されます。
このようにして、 4冊の本の最初の設計原則に従います"実装ではなく、インターフェースへのプログラム"。
読む必要があります(アクセスできる場合)効果的なJava 2 項目1:コンストラクターではなく静的ファクトリーメソッドを検討する。
静的ファクトリーメソッドの利点:
- 名前があります。
- これらは、呼び出されるたびに新しいオブジェクトを作成する必要はありません。
- これらは、戻り型の任意のサブタイプのオブジェクトを返すことができます。
- これらは、パラメータ化された型インスタンスの作成の冗長性を減らします。
静的ファクトリメソッドの欠点:
- 静的ファクトリメソッドのみを提供する場合、パブリックコンストラクターまたは保護されたコンストラクターのないクラスはサブクラス化できません。
- これらは他の静的メソッドと簡単に区別できません
デフォルトでは、コンストラクターの方がわかりやすく、記述しやすいため、コンストラクターを優先する必要があります。ただし、クライアントコードが理解するセマンティックな意味からオブジェクトの構造の良さを明確に分離する必要がある場合は、ファクトリを使用した方が良いでしょう。
コンストラクタとファクトリの違いは、たとえば変数と変数へのポインタに似ています。別のレベルの間接参照がありますが、これは欠点です。しかし、別のレベルの柔軟性もあり、これは利点です。したがって、選択する際には、この費用対利益の分析を行うことをお勧めします。
「Effective Java」第2版、項目1: 5:
" 静的ファクトリメソッドはファクトリメソッドパターンと同じではないことに注意してください デザインパターンから [Gamma95、p。 107]。で説明されている静的ファクトリーメソッド このアイテムには、デザインパターンに直接相当するものはありません。"
ファクトリを使用するのは、コンストラクターでは実行できない方法で、オブジェクト作成で追加の制御が必要な場合のみです。
たとえば、ファクトリにはキャッシュの可能性があります。
ファクトリを使用する別の方法は、構築するタイプがわからないシナリオです。多くの場合、各プラグインがベースクラスから派生するか、何らかのインターフェイスを実装する必要があるプラグインファクトリシナリオで、このタイプの使用法を確認します。ファクトリーは、ベースクラスから派生するか、インターフェースを実装するクラスのインスタンスを作成します。
CAD / CAMアプリケーションの具体例。
切断パスは、コンストラクターを使用して作成されます。カットするパスを定義する一連の線と円弧です。一連の線と円弧は異なる場合があり、異なる座標を持つことができますが、リストをコンストラクターに渡すことで簡単に処理できます。
形状は、工場を使用して作成されます。シェイプクラスが存在する間、各シェイプは、シェイプのタイプに応じて異なる方法でセットアップされるためです。ユーザーが選択するまで、どの形状を初期化するのかわかりません。
" effective java" (別の回答で述べたように)に加えて、別の古典的な本も提案しています:
オーバーロードされたコンストラクターよりも(引数を説明する名前の)静的ファクトリーメソッドを優先します。
たとえば書かないでください
Complex complex = new Complex(23.0);
代わりに書く
Complex complex = Complex.fromRealNumber(23.0);
この本は、 Complex(float)
コンストラクターをプライベートにして、ユーザーに静的ファクトリーメソッドを呼び出させることを提案しているだけです。
さて、id値を期待するクラスのコンストラクターがあります。コンストラクターはこの値を使用して、データベースからクラスを作成します。
このプロセスは、必ずコンストラクターの外部にある必要があります。
-
コンストラクタはデータベースにアクセスしないでください。
-
コンストラクタのタスクと理由は、コンストラクタに渡された値を使用してデータメンバーを初期化し、クラス不変量を確立することです。
-
他のすべての場合、より良いアプローチは、静的ファクトリーメソッドを使用するか、より複雑な場合は別の factory または builder クラスを使用することです。
Microsoftのいくつかのコンストラクターガイドライン行:
コンストラクタで最小限の作業を行います。コンストラクターは、コンストラクターのパラメーターをキャプチャする以外の多くの作業を行うべきではありません。他の処理のコストは、必要になるまで遅らせる必要があります。
そして
目的の操作のセマンティクスが新しいインスタンスの構築に直接マップされない場合は、コンストラクターではなく静的ファクトリーメソッドの使用を検討してください。
オブジェクトの作成中に、いくつかの値/条件を確認/計算する必要がある場合があります。そして、例外をスローできる場合、constructroは非常に悪い方法です。したがって、次のようにする必要があります。
var value = new Instance(1, 2).init()
public function init() {
try {
doSome()
}
catch (e) {
soAnotherSome()
}
}
追加の計算はすべてinit()で行われます。しかし、開発者としてのあなただけが本当にこのinit()について知っています。そしてもちろん、数ヶ月後には忘れてしまいます。 ただし、ファクトリーがある場合は、このinit()を直接呼び出しから隠すことで、1つのメソッドで必要なことをすべて行うだけなので、問題はありません。このアプローチを使用すると、作成に失敗したり、メモリリークが発生しても問題はありません。
誰かがキャッシングについて教えてくれました。それは良いです。ただし、Flyweightパターンについても覚えておく必要があります。これはFactoryの方法で使用すると便利です。