不変オブジェクトのインターフェイスを宣言しないでください


27

不変オブジェクトのインターフェイスを宣言しないでください

[編集] 問題のオブジェクトがデータ転送オブジェクト(DTO)またはプレーンオールドデータ(POD)を表す場合

それは合理的なガイドラインですか?

これまで、不変の(データを変更できない)シールクラスのインターフェイスを作成することがよくありました。私は、不変性を気にするところにはインターフェースを使用しないように注意しました。

残念ながら、インターフェイスはコードに浸透し始めます(心配しているのは私のコードだけではありません)。インターフェースを渡された後、渡されるものが不変であると本当に想定したいコードにそれを渡したいと思うでしょう。

この問題のため、不変オブジェクトのインターフェイスを宣言しないことを検討しています。

これは単体テストに関して悪影響があるかもしれませんが、それ以外に、これは合理的なガイドラインに思えますか?

または、私が見ている「拡散インターフェース」の問題を回避するために使用すべき別のパターンがありますか?

(私はこれらの不変オブジェクトをいくつかの理由で使用しています:主にスレッドセーフのために、私は多くのマルチスレッドコードを書いていますが、それはまた、メソッドに渡されるオブジェクトの防御的なコピーの作成を避けることができるためです。コードは何かが不変であることを知っている多くの場合-インターフェイスを渡された場合はそうではありません。実際、インターフェイスを介して参照されるオブジェクトの防御コピーを作成することさえできません。クローン操作またはそれをシリアル化する方法...)

[編集]

オブジェクトを不変にしたいという私の理由により多くのコンテキストを提供するには、Eric Lippertの次のブログ投稿を参照してください。

http://blogs.msdn.com/b/ericlippert/archive/tags/immutability/

また、ここでは、マルチスレッドジョブキューで操作/受け渡しされるアイテムなど、いくつかの低レベルの概念で作業していることを指摘する必要があります。これらは本質的にDTOです。

また、Joshua Bloch は、著書Effective Javaで不変オブジェクトの使用を推奨しています


ファローアップ

フィードバックをありがとう、すべて。先に進み、このガイドラインをDTOとその同類に使用することにしました。これまでのところ順調に機能していますが、1週間しか経っていません...それでも、見栄えは良いです。

これに関連する他の問題がいくつかあります。特に私が「ディープまたはシャロー不変性」と呼んでいるもの(ディープアンドシャロークローンから盗んだ命名法)-しかし、それはまた別の質問です。


3
+1素晴らしい質問。オブジェクトが同じインターフェースを単に実装するものではなく、特定の不変クラスのものであることがなぜあなたにとって非常に重要であるかを説明してください。問題の原因が他の場所にある可能性があります。依存性注入は、単体テストにとって非常に重要ですが、コードに特定の不変クラスを要求するように強制すると消える他の多くの重要な利点があります。
スティーブン・ドッグガート

1
不変という用語の使用に少し混乱しています。明確にするために、継承およびオーバーライドできないシールクラスを意味しますか、またはそれが公開するデータが読み取り専用であり、オブジェクトの作成後に変更できないことを意味しますか?言い換えると、オブジェクトが特定のタイプであること、またはその値が決して変わらないことを保証したいのですか。私の最初のコメントでは、前者を意味すると想定していましたが、今の編集では後者のように聞こえます。それとも両方に関心がありますか?
スティーブンドッグガート

3
私は混乱しています。なぜセッターをインターフェイスから外さないのですか?しかし一方で、実際にはドメインオブジェクトであるオブジェクトや、それらのオブジェクトのファクトリーを必要とするDTOのインターフェースを見るのにうんざりしています。
マイケルブラウン

2
すべて不変のクラスのインターフェースはどうですか?たとえば、Javaの数値クラスには1つが定義することができますList<Number>保持できるIntegerFloatLongBigDecimal、等...すべてが自分自身不変です。

1
@MikeBrownインターフェースが不変性を意味するからといって、実装がそれを強制することを意味しないと思います。単に慣習に任せることができます(つまり、不変性が要件であるというインターフェースを文書化します)が、誰かが慣習に違反した場合、いくつかの本当に厄介な問題が発生する可能性があります。
-vaughandroid

回答:


17

私の意見では、あなたのルールは良いルール(または少なくとも悪いルールではありません)ですが、それはあなたが説明している状況のためだけです。私はすべての状況でこれに同意するとは言いませんので、私の内省者の観点から、あなたの規則は技術的に広すぎると言わざるを得ません。

通常、データ転送オブジェクト(DTO)として本質的に使用されない限り、不変オブジェクトは定義しません。つまり、データプロパティは含まれますが、ロジックはほとんどなく、依存関係はありません。その場合、ここにあるように見えるので、インターフェイスではなく具体的​​な型を直接使用しても安全だと思います。

意見が一致しない単体テストの純粋主義者もいるはずですが、私の意見では、DTOクラスは単体テストおよび依存関係注入の要件から安全に除外できます。DTOには依存関係がないため、ファクトリを使用してDTOを作成する必要はありません。必要に応じてすべてが直接DTOを作成する場合、とにかく別のタイプを注入する方法は実際にはないため、インターフェイスは必要ありません。そして、それらにはロジックが含まれていないため、ユニットテストを行う必要はありません。依存関係がない限り、何らかのロジックが含まれていても、必要に応じてロジックの単体テストを行うのは簡単です。

そのため、すべてのDTOクラスがインターフェイスを実装しないというルールを作成しても、潜在的に不要ではあるが、ソフトウェア設計を損なうことはないと思います。データは不変である必要があるという要件があり、インターフェイスを介してそれを強制することはできないため、その規則をコーディング標準として確立することは完全に正当であると言えます。

ただし、より大きな問題は、クリーンなDTOレイヤーを厳密に適用する必要があることです。インターフェイスなしの不変クラスがDTOレイヤーにのみ存在し、DTOレイヤーにロジックと依存関係がない限り、安全です。ただし、レイヤーの混合を開始し、ビジネスレイヤークラスを兼ねるインターフェイスのないクラスがある場合は、多くの問題が発生し始めると思います。


特にDTOまたは不変の値オブジェクトを処理することを期待するコードは、インターフェイスの機能ではなく、そのタイプの機能を使用する必要がありますが、そのようなクラスによって実装されるインターフェイスの使用例がないことも意味します他のクラスによる、一定の最短時間有効な値を返すことを約束するメソッド(たとえば、インターフェイスが関数に渡される場合、メソッドはその関数が戻るまで有効な値を返す必要があります)。同様のデータと一つの願いをカプセル化タイプ...の数が存在する場合、このようなインターフェイスは有用であることができる
supercat

...一方から他方へデータをコピーする手段を持つ。特定のデータを含むすべてのタイプがそれを読み取るために同じインターフェースを実装している場合、そのようなデータはすべてのタイプ間で簡単にコピーできます。
supercat 14

その時点で、ゲッター(およびDTOが何らかの愚かな理由で可変である場合はセッター)を削除し、フィールドをパブリックにすることもできます。そこにロジックを配置することはありませんよね?
ケビン

@Kevinと思いますが、自動プロパティの現代的な利便性により、プロパティを作成するのはとても簡単です、なぜですか?パフォーマンスと効率が最大の関心事であれば、おそらくそれが重要だと思います。いずれにせよ、問題は、DTOでどのレベルの「ロジック」を許可するかです。プロパティに検証ロジックを含めても、モックを必要とする依存関係がなければ、ユニットテストを実行しても問題ありません。DTOが依存関係のビジネスオブジェクトを使用しない限り、テスト可能なままであるため、小さなロジックを組み込むことは安全です。
スティーブンドッグガート

個人的には、そのようなものをできるだけきれいに保つことを好みますが、ルールを曲げる必要がある場合が常にあるので、万が一に備えてドアを開けたままにしておく方が良いでしょう。
スティーブンドッグガート

7

私は自分のコードを悪用し難くすることについて大騒ぎしていました。変化するメンバーを隠すための読み取り専用インターフェイスを作成し、一般的な署名などに多くの制約を追加しました。ほとんどの場合、想像上の同僚を信頼していなかったため、ほとんどの場合設計を決定しました。「おそらくいつか彼らは新しいエントリーレベルの人を雇うだろうし、クラスXYZがDTO ABCを更新できないことを彼は知らないだろう。他の場合、私は間違った問題に焦点を合わせていました-明らかな解決策を無視して-木を通して森を見ません。

DTOのインターフェイスを作成することはもうありません。私は、コードに触れる人々(主に自分自身)が何が許可され、何が理にかなっているかを知っているという仮定の下で働いています。私が同じ愚かな間違いを犯し続けている場合、私は通常、インターフェイスを強化しようとはしません。今、私はほとんどの時間を使って、なぜ同じ間違いを犯し続けるのかを理解しようとしています。それは通常、私が何かを分析しすぎているか、重要な概念を逃しているからです。妄想をあきらめたので、私のコードはずっと使いやすくなりました。また、システムで作業するためにインサイダーの知識が必要な「フレームワーク」が少なくなります。

私の結論は、最も単純なものを見つけることです。安全なインターフェイスを作成するために追加された複雑さは、開発時間を無駄にし、それ以外の場合は単純なコードを複雑にします。10,000人の開発者がライブラリを使用している場合、このようなことを心配してください。私を信じて、それは多くの不必要な緊張からあなたを救います。


通常、ユニットテストに依存性注入が必要な場合に備えて、インターフェイスを保存します。
トラビスパークス

5

大丈夫なガイドラインのようですが、奇妙な理由があります。インターフェース(または抽象基本クラス)が一連の不変オブジェクトへの均一なアクセスを提供する場所がいくつかありました。戦略はここに落ちる傾向があります。状態オブジェクトはここに落ちる傾向があります。インターフェースを不変に見えるように整形し、それをAPIにそのように文書化するのは理にかなっているとは思いません。

そうは言っても、人々は、Plain Old Data(以降、POD)オブジェクト、さらには単純な(多くの場合不変の)構造さえもオーバーインターフェースする傾向があります。コードに何らかの基本的な構造に対する適切な代替手段がない場合、インターフェイスは必要ありません。いいえ、ユニットテストはデザインを変更するのに十分な理由ではありません(データベースアクセスをモックすることは、インターフェイスを提供する理由ではなく、将来の変更に対する柔軟性です)-テストが世界の終わりではない基本的な基本構造をそのまま使用します。


2

何かが不変であることがわかっている場合、多くの場合、コードは非常に単純になります。これは、インターフェイスを渡された場合はそうではありません。

アクセサの実装者が心配する必要があるとは思いません。interface X不変であることが意図されている場合、不変の方法でインターフェースを実装することを保証するのは、インターフェース実装者の責任ではありませんか?

ただし、私の考えでは、不変のインターフェイスなどはありません。インターフェイスで指定されたコードコントラクトは、オブジェクトの公開されたメソッドにのみ適用され、オブジェクトの内部については何も適用されません。

インターフェースではなくデコレータとして実装された不変性を見るのが一般的ですが、そのソリューションの実現可能性はオブジェクトの構造と実装の複雑さに依存します。


1
デコレータとして不変性を実装する例を提供できますか?
vaughandroid

自明、私は思いませんが、それは比較的単純な思考の練習だではない-場合MutableObjectがあるn変更状態とすることをメソッドmメソッドの戻り状態は、というImmutableDecoratorリターン状態(そのメソッド公開し続けることができますm)、そして、環境に応じて、アサートまたはを可変メソッドの1つが呼び出されたときに例外をスローします。
ジョナサンリッチ

私は本当に...実行時例外の可能性にコンパイル時の確実性を変換し好きではない
マシュー・ワトソン

OK、しかし、ImmutableDecoratorは特定のメソッドが状態を変更するかどうかをどのようにして知ることができますか?
vaughandroid

インターフェースを実装するクラスが、インターフェースで定義されたメソッドに関して不変であるかどうかは、どのインスタンスでも確認できません。@Baquetaデコレータには、基本クラスの実装に関する知識が必要です。
ジョナサンリッチ

0

インターフェースを渡された後、渡されるものが不変であると本当に想定したいコードにそれを渡したいと思うでしょう。

C#では、「不変」型のオブジェクトを予期するメソッドは、C#インターフェイスが不変性のコントラクトを指定できないため、インターフェイス型のパラメーターを持つべきではありません。したがって、あなたが提案しているガイドラインは、そもそもそれを行うことができないため、C#では無意味です(そして、言語の将来のバージョンではそれを可能にする可能性は低いです)。

あなたの質問は、不変性に関するEric Lippertの記事の微妙な誤解から生じています。エリックはインターフェイスIStack<T>を定義せずIQueue<T>、不変のスタックとキューのコントラクトを指定しませんでした。彼らはしません。彼は便宜上それらを定義しました。これらのインターフェースにより、彼は空のスタックとキューに異なるタイプを定義することができました。単一の型を使用して、空のスタックを表すためにインターフェイスまたは別の型を必要としない不変スタックの異なる設計と実装を提案できますが、結果のコードはきれいに見えず、少し効率が悪くなります。

それでは、エリックのデザインにこだわりましょう。不変スタックを必要とするメソッドには、一般的な意味でスタックの抽象データ型を表すStack<T>一般的なインターフェースIStack<T>ではなく、型のパラメーターが必要です。Ericの不変スタックを使用するときにこれをどのように行うかは明らかではなく、彼は記事でこれについて議論しませんでしたが、可能です。問題は、空のスタックのタイプにあります。空のスタックを取得しないようにすることで、この問題を解決できます。これは、スタックの最初の値としてダミー値をプッシュし、決してポップしないことで保証できます。このように、あなたは安全の結果にキャストすることができますPushし、PopにをStack<T>

持つStack<T>道具はIStack<T>便利です。スタック、任意のスタックを必要とするメソッドを定義できます。必ずしも不変スタックではありません。これらのメソッドは、typeのパラメーターを持つことができますIStack<T>。これにより、不変スタックを渡すことができます。理想的にIStack<T>は、標準ライブラリ自体の一部になります。.NETにはありませんIStack<T>が、不変スタックが実装できる他の標準インターフェイスがあり、型がより便利になります。

先ほど言及した不変スタックの代替設計と実装では、というインターフェイスを使用しますIImmutableStack<T>。もちろん、インターフェイス名に「Immutable」を挿入しても、それを実装するすべての型が不変になるわけではありません。ただし、このインターフェイスでは、不変性のコントラクトは単なる口頭です。優れた開発者はそれを尊重する必要があります。

小規模な内部ライブラリを開発している場合は、チームの全員に同意してこの契約を尊重しIImmutableStack<T>、パラメーターのタイプとして使用できます。それ以外の場合は、インターフェイスタイプを使用しないでください。

質問C#にタグを付けたので、C#仕様にはDTOやPODなどは存在しないことを付け加えます。したがって、それらを破棄するか、正確に定義すると、問題が改善されます。

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