不変オブジェクトのインターフェースを宣言しないでください
[編集]問題のオブジェクトがデータ転送オブジェクト(DTO)またはプレーンオールドデータ(POD)を表す場合
それは妥当なガイドラインですか?
これまで、私は多くの場合、不変の(データを変更できない)密封されたクラスのインターフェースを作成してきました。私は自分が不変性を気にする場所でインターフェイスを使用しないように自分自身に気を付けようとしました。
残念ながら、インターフェースはコードに浸透し始めます(そしてそれは私が心配している私のコードだけではありません)。あなたはインターフェースを渡されてから、それに渡されるものが不変であると本当に想定したいいくつかのコードにそれを渡したいと思っています。
この問題のため、不変オブジェクトのインターフェースを宣言しないことを検討しています。
これは単体テストに関して影響があるかもしれませんが、それ以外は、これは妥当なガイドラインのように見えますか?
または、私が見ている「拡散インターフェース」の問題を回避するために使用する必要がある別のパターンはありますか?
(これらの不変オブジェクトを使用している理由はいくつかあります。主にスレッドセーフのために、多数のマルチスレッドコードを記述していますが、メソッドに渡されるオブジェクトの防御的なコピーを作成する必要がないことも意味します。コードは何かが不変であることがわかっている多くの場合-インターフェースを渡された場合はそうではありません。実際、インターフェースを介して参照されているオブジェクトの防御コピーを作成できない場合は、クローン操作またはそれをシリアル化する任意の方法...)
[編集]
オブジェクトを不変にしたいという私の理由からより多くのコンテキストを提供するには、Eric Lippertからの次のブログ投稿を参照してください。
http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/
ここでは、マルチスレッドのジョブキューで操作/受け渡しされるアイテムなど、いくつかの下位レベルの概念を使用していることも指摘しておきます。これらは基本的にDTOです。
また、Joshua Bloch 彼の本「Effective Java」で不変オブジェクトの使用を推奨しています 。
フォローアップ
フィードバックありがとうございます。私は先に進んで、このガイドラインをDTOとその同類に使用することにしました。今のところ順調に機能していますが、まだ1週間しか経っていません...それでも見栄えは良いです。
これに関連して、他にも質問したい問題があります。特に私が「ディープまたはシャロー不変性」と呼んでいるもの(私がディープアンドシャロークローニングから盗んだ命名法)ですが、それはまた別の問題です。
私の意見では、あなたのルールは良いものです(少なくともそれは悪いものではありません)。すべての状況で私がそれに同意するとは言いません。ですから、私の内側の立場からすると、あなたのルールは技術的に広すぎると言わざるを得ません。
通常、不変オブジェクトは、本質的にデータ転送オブジェクト(DTO)として使用されていない限り定義しません。つまり、オブジェクトにはデータプロパティが含まれますが、ロジックはほとんどなく、依存関係はありません。その場合、ここにあるように見えるので、インターフェイスではなく具象型を直接使用しても安全だと思います。
私は反対するユニットテストの純粋主義者がいると確信していますが、私の意見では、DTOクラスはユニットテストと依存性注入の要件から安全に除外できます。依存関係がないため、DTOを作成するためにファクトリを使用する必要はありません。すべてが必要に応じてDTOを直接作成する場合は、とにかく別のタイプを挿入する方法は実際にはないため、インターフェースは必要ありません。また、これらにはロジックが含まれていないため、単体テストを行う必要はありません。依存関係がない限り、ロジックが含まれていても、必要に応じてロジックを単体テストすることは簡単です。
そのため、すべてのDTOクラスがインターフェイスを実装しないというルールを作成しても、潜在的には不要ですが、ソフトウェア設計に悪影響を与えることはないと思います。データが不変である必要があるというこの要件があり、インターフェースを介してそれを強制することはできないので、私はそのルールをコーディング標準として確立することは完全に正当だと思います。
ただし、より大きな問題は、クリーンなDTOレイヤーを厳密に適用する必要があることです。インターフェイスなしの不変クラスがDTOレイヤーにのみ存在し、DTOレイヤーにロジックと依存関係がないままである限り、安全です。ただし、レイヤーの混合を開始し、ビジネスレイヤークラスを兼ねるインターフェイスのないクラスがある場合は、多くの問題が発生し始めると思います。
私は自分のコードを誤用に対して無防備にすることについて大騒ぎしていました。変化するメンバーを非表示にするための読み取り専用インターフェイスを作成したり、ジェネリックシグネチャに多くの制約を追加したりしました。ほとんどの場合、架空の同僚を信用していなかったため、設計の決定を下しました。 「おそらくいつか彼らは新しい入門レベルの男を雇うでしょう、そして彼はクラスXYZがDTO ABCを更新できないことを知らないでしょう。他の時には私は間違った問題に焦点を合わせていました-明白な解決策を無視して-木々から森を見ないで。
DTOのインターフェースを作成することはもうありません。私は、コードに触れる人(主に私)が何が許可され、何が理にかなっているかを知っているという前提の下で作業します。私が同じ愚かな間違いをし続けるなら、私は通常自分のインターフェースを強化しようとはしません。今、私はほとんどの時間を費やして、なぜ同じ過ちを犯し続けるのかを理解しようとしています。それは通常、私が何かを過度に分析している、または重要な概念を見逃しているためです。私は偏執狂であることをあきらめて以来、私のコードははるかに扱いやすくなっています。また、システムで作業するために内部関係者の知識を必要とする「フレームワーク」も少なくなります。
私の結論は、機能する最も単純なものを見つけることでした。安全なインターフェイスを作成するために追加された複雑さは、開発時間を浪費し、他の点では単純なコードを複雑にします。ライブラリを使用する開発者が10,000人いる場合、このようなことが心配になります。私を信じて、それは多くの不必要な緊張からあなたを救うでしょう。
それは大丈夫なガイドラインのように見えますが、奇妙な理由があります。インターフェイス(または抽象基本クラス)が一連の不変オブジェクトへの均一なアクセスを提供する場所はたくさんありました。戦略はここに落ちる傾向があります。状態オブジェクトはここに落ちる傾向があります。インターフェースを不変に見えるように形成し、それをAPIでそのように文書化するのは不合理ではないと思います。
とはいえ、人々はプレーン・オールド・データ(以下、POD)オブジェクトや、単純な(多くの場合不変の)構造さえもインターフェースしすぎる傾向があります。コードに基本的な構造の健全な代替手段がない場合は、インターフェースは必要ありません。いいえ、単体テストは設計を変更するのに十分な理由ではありません(データベースアクセスのモックが、それにインターフェースを提供する理由ではありません。将来の変更に柔軟に対応できます)-テストが世界の終わりではないその基本的な基本構造をそのまま使用します。
何かが不変であることがわかっている場合、多くの場合、コードははるかに単純になります。インターフェイスを渡された場合はそうではありません。
これは、アクセサーの実装者が心配する必要のある問題ではないと思います。 interface X
は不変であることを意図していますが、不変の方法でインターフェースを実装することを保証するのはインターフェース実装者の責任ではないでしょうか?
ただし、私の考えでは、不変のインターフェイスなどはありません。インターフェイスによって指定されたコードコントラクトは、オブジェクトの公開されたメソッドにのみ適用され、オブジェクトの内部については何も適用されません。
不変性がインターフェースではなくデコレーターとして実装されるのが一般的ですが、そのソリューションの実現可能性は、実際にはオブジェクトの構造と実装の複雑さに依存します。
あなたはインターフェースを渡されてから、それに渡されるものが不変であると本当に想定したいいくつかのコードにそれを渡したいと思っています。
C#では、「不変」タイプのオブジェクトを必要とするメソッドに、インターフェースタイプのパラメーターを含めないでください。C#インターフェースは、不変コントラクトを指定できないためです。したがって、そもそも提案しているガイドラインはC#では意味がありません。そもそもそれを行うことはできないためです(そして、言語の将来のバージョンではそれを可能にする可能性は低いです)。
あなたの質問は、Eric Lippertの不変性に関する記事の微妙な誤解から生じています。エリックは、不変のスタックとキューのコントラクトを指定するために、インターフェイスIStack<T>
およびIQueue<T>
を定義しませんでした。彼らはしません。彼は便宜上それらを定義しました。これらのインターフェースにより、空のスタックとキューにさまざまなタイプを定義できるようになりました。空のスタックを表すためのインターフェースや個別のタイプを必要とせずに単一のタイプを使用して、不変スタックの 異なる設計と実装 を提案できますが、結果のコードはきれいに見えず、少し効率が悪い。
それでは、エリックのデザインに固執しましょう。不変スタックを必要とするメソッドには、一般的な意味でスタックの抽象データ型を表す一般的なインターフェイスStack<T>
ではなく、タイプIStack<T>
のパラメーターが必要です。エリックの不変スタックを使用するときにこれを行う方法は明らかではなく、彼は彼の記事でこれについて説明しませんでしたが、それは可能です。問題は、空のスタックのタイプにあります。空のスタックを取得しないようにすることで、この問題を解決できます。これは、スタックの最初の値としてダミー値をプッシュし、それをポップしないことで保証できます。このようにして、Push
およびPop
の結果をStack<T>
に安全にキャストできます。
Stack<T>
にIStack<T>
を実装させると便利です。スタック、任意のスタック、必ずしも不変のスタックを必要としないメソッドを定義できます。これらのメソッドは、タイプIStack<T>
のパラメーターを持つことができます。これにより、不変スタックを渡すことができます。理想的には、IStack<T>
は標準ライブラリ自体の一部になるでしょう。 .NETにはIStack<T>
はありませんが、不変スタックが実装できる他の標準インターフェイスがあり、型がより便利になります。
以前に参照した不変スタックの代替設計と実装では、IImmutableStack<T>
というインターフェースを使用しています。もちろん、インターフェース名に「Immutable」を挿入しても、それを実装するすべての型が不変になるわけではありません。ただし、このインターフェースでは、不変性の契約は言葉に過ぎません。優れた開発者はそれを尊重する必要があります。
小さな内部ライブラリを開発している場合は、チームの全員に同意してこの契約を尊重し、IImmutableStack<T>
をパラメータのタイプとして使用できます。それ以外の場合は、インターフェイスタイプを使用しないでください。
質問C#にタグを付けたので、C#仕様にはDTOやPODなどはありません。したがって、それらを破棄するか正確に定義すると、問題が改善されます。