コンストラクターの複雑さ


18

私は、コンストラクターがどれだけの作業を行えるかについて同僚と議論しています。内部に別のオブジェクトAを必要とするクラスBがあります。オブジェクトAは、クラスBがその仕事をするために必要な数少ないメンバーの1つです。そのパブリックメソッドはすべて内部オブジェクトAに依存します。オブジェクトAに関する情報はDBに格納されているので、コンストラクターでDBを検索して検証および取得を試みます。私の同僚は、コンストラクターがコンストラクターのパラメーターをキャプチャする以外に多くの作業を行うべきではないと指摘しました。コンストラクターへの入力を使用してオブジェクトAが見つからない場合、すべてのパブリックメソッドはとにかく失敗するため、インスタンスを作成して後で失敗させるのではなく、実際にはコンストラクターで早期にスローする方がよいと主張しました。

他の人はどう思いますか?それが違いを生む場合は、C#を使用しています。

読書オブジェクトのすべての作業をコンストラクターで行う理由はありますか?ユーザーがコンストラクタに間違った値を渡した場合、そのパブリックメソッドを使用できないため、DBにアクセスしてオブジェクトAを取得することは「オブジェクトを使用可能にするために必要な他の初期化」の一部です。

コンストラクターは、オブジェクトのフィールドをインスタンス化し、オブジェクトを使用可能にするために必要な他の初期化を行う必要があります。これは一般に、コンストラクターが小さいことを意味しますが、これがかなりの量の作業になるシナリオがあります。


8
同意しません。依存関係の反転の原理を見てください。オブジェクトAがコンストラクターで有効な状態でオブジェクトBに渡されるとよいでしょう。
ジミーホファ

5
どれだけできるかを尋ねていますか?どれくらいすべきですか?または、あなたの特定の状況のどれくらい行うべきですか?それは3つの非常に異なる質問です。
ボブソン

1
私は、依存関係の注入(または「依存関係の逆転」)を検討するというジミーホファの提案に同意します。それは説明を読むときの私の最初の考えでした。クラスBが使用している機能をクラスAに分離しています。あなたの場合には話せませんが、依存性注入パターンを使用するようにコードをリファクタリングすると、クラスがより簡単に/より複雑になります。
ワイリー博士の弟子、

1
ボブソン、タイトルを「should」に変更しました。ここでベストプラクティスを求めています。
テインキム

1
@JimmyHoffa:コメントを回答に展開する必要があるかもしれません。
ケビンクライン14年

回答:


22

あなたの質問は、2つの完全に独立した部分から構成されています。

コンストラクターから例外をスローする必要がありますか、またはメソッドを失敗させる必要がありますか?

これは明らかに、フェイルファーストの原則の適用です。コンストラクターを失敗させることは、メソッドが失敗する理由を見つけることよりもデバッグがはるかに簡単です。たとえば、コードの他の部分から既に作成されたインスタンスを取得し、メソッドを呼び出すときにエラーが発生する場合があります。オブジェクトが間違って作成されていることは明らかですか?番号。

「コールをtry / catchでラップする」問題に関しては。例外があることを意味する例外。一部のコードが例外をスローすることがわかっている場合は、try / catchでコードをラップしませんが、例外をスローできるコードを実行する前にそのコードのパラメーターを検証します。例外は、システムが無効な状態にならないようにする方法としてのみ意味されます。一部の入力パラメーターが無効な状態につながる可能性があることがわかっている場合は、それらのパラメーターが発生しないようにします。この方法では、通常はシステムの境界上にある例外を論理的に処理できる場所でtry / catchを実行するだけで済みます。

コンストラクターから「DBのようなシステムの他の部分」にアクセスできますか。

これは驚くことの原則に反すると思います。コンストラクターがDBにアクセスすることを期待する人はあまりいません。だから、それをするべきではない。


2
これに対するより適切なソリューションは、通常、構築が無料の「オープン」タイプのメソッドを追加することですが、実際の初期化がすべて行われる「オープン」/「接続」/などの前に使用することはできません。コンストラクターの失敗は、将来オブジェクトの部分的な構築を行いたいときに、あらゆる種類の問題を引き起こす可能性があります。これは便利な機能です。
ジミーホファ

@JimmyHoffaコンストラクターメソッドと特定の構築メソッドの間に違いはありません。
陶酔

4
できないかもしれませんが、多くの人がそうします。接続オブジェクトを作成するとき、コンストラクターはそれを接続しますか?接続されていないオブジェクトは使用可能ですか?どちらの質問でも、答えは「いいえ」です。接続を開く前に設定や物事を微調整できるように、接続オブジェクトを部分的に構築しておくと便利です。これは、このシナリオへの一般的なアプローチです。オブジェクトAにはリソース依存関係があるが、コンストラクターに危険な作業をさせるよりもopenメソッドの方が優れているシナリオでは、コンストラクター注入は依然として正しい方法だと思います。
ジミーホッファ

@JimmyHoffaただし、これは接続クラスの特定の要件であり、OOP設計の一般的な規則ではありません。実際には、ルールというよりも例外的なケースだと思います。あなたは基本的にビルダーパターンについて話している。
陶酔

2
これは要件ではなく、設計上の決定でした。コンストラクターに接続文字列を取得させ、構築されたときにすぐに接続させることができます。それは、接続文字列または他のビットとbobblesて微調整フィギュア、その後、オブジェクトを作成することができ、それを接続するのに便利ですので、彼らはあまりにもないことを決めた後に
ジミー・ホッファ

19

あー

コンストラクターはできる限り実行すべきではありません-コンストラクターでの例外動作が厄介であるという問題があります。適切な動作が何であるか(継承シナリオを含む)を知っているプログラマーは非常に少なく、ユーザーにすべてのインスタンス化を試行/キャッチするように強制するのは、せいぜい苦痛です。

これに、コンストラクターとコンストラクターの使用という2つの部分があります。コンストラクターでは、例外が発生したときに多くのことを実行できないため、扱いにくいです。別の型を返すことはできません。nullを返すことはできません。基本的に、例外を再スローしたり、壊れたオブジェクトを返したり(不正なコンストラクター)、(ベストケース)一部の内部パーツを正常なデフォルトに置き換えたりすることができます。インスタンス化(およびすべての派生型のインスタンス化)がスローされる可能性があるため、コンストラクターの使用は厄介です。その後、メンバー初期化子では使用できません。ロジックの例外を使用して、どこでもtry / catchによって引き起こされる可読性(および脆弱性)を超えて、「作成が成功したかどうか」を確認します。

コンストラクターが自明ではないこと、または合理的に失敗すると予想されることを行っている場合は、Create代わりに静的メソッド(非パブリックコンストラクター)を検討してください。はるかに使いやすく、デバッグが簡単で、成功した場合でも完全に構築されたインスタンスを取得できます。


2
あなたがここで言及したような創造パターンが存在する理由は、まさに複雑な構築シナリオです。これは、これらの問題のある構造に対する優れたアプローチです。コマンドが適切に接続されていることがわかるように、.NETでConnectionオブジェクトをどのように使用できるかを考えるCreateCommand必要があります。
ジミーホファ

2
これに加えて、データベースは重い外部依存関係であり、将来のある時点でデータベースを切り替え(またはテスト用にオブジェクトをモック)、この種のコードをFactoryクラス(またはIServiceのようなもの)に移動するとしますプロバイダー)よりクリーンなアプローチのようです
ジェイソン・スパースケ14年

4
「コンストラクターの例外動作は厄介です」について詳しく説明できますか?Createを使用する場合、コンストラクターへの呼び出しをラップするのと同じように、try catchでラップする必要はありませんか?Createは、コンストラクターの名前とは異なるように感じます。たぶん私はあなたの答えを正しく得ていませんか?
テインキム

1
コンストラクターからバブリングする例外をキャッチする必要があったため、これに対する引数に+1しました。ただし、「コンストラクターで動作しません」と言う人と作業を行ったため、これは奇妙なパターンを作成する可能性もあります。私はすべてのオブジェクトがコンストラクターだけでなく、Initialize()の何らかの形式も持っているコードを見てきました。オブジェクトは、それが呼び出されるまで実際には有効ではありません。そして、呼び出すべきものが2つあります:ctorとInitialize()です。オブジェクトを設定するために作業を行う問題は消えません-C#はC ++とは異なる解決策をもたらすかもしれません。工場は良いです!
J Trana

1
@jtrana-はい、初期化は良くありません。コンストラクターは、適切なデフォルトまたはファクトリーに初期化するか、またはcreateメソッドがビルドして、使用可能なインスタンスを返します。
テラスティン

1

コンストラクター-メソッドの複雑さのバランスは、確かに良いスタイルに関する議論の問題です。かなり頻繁に空のコンストラクターClass() {}が使用され、Init(..) {..}より複雑なことのためのメソッドが使用されます。考慮すべき引数の中には:

  • クラスのシリアル化の可能性
  • Initメソッドをコンストラクターから分離するとClass x = new Class(); x.Init(..);、ユニットテストで部分的に同じシーケンスを何度も繰り返す必要があります。しかし、それほど良くはありませんが、読むのには非常にクリアです。時々、私はそれらを1行のコードに入れるだけです。
  • ユニットテストの目的が単なるクラス初期化テストである場合、独自のInitを持ち、中間のAssertを使用して、より複雑なアクションを1つずつ実行する方が適切です。

私の意見では、このinit方法の利点は、障害の処理がはるかに簡単になることです。特に、initメソッドの戻り値は初期化が成功したかどうかを示し、メソッドはオブジェクトが常に良好な状態であることを保証できます。
pqnet
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.