プロパティの1つが必要ない場合のインターフェイスの実装


31

とても簡単です。私はインターフェイスを実装していますが、このクラスには不要なプロパティが1つあり、実際には使用しないでください。私の最初のアイデアは、次のようなことをすることでした。

int IFoo.Bar
{
    get { raise new NotImplementedException(); }
}

これ自体に問題はないと思いますが、「正しい」とは感じません。他の誰かが以前に同様の状況に遭遇したことはありますか?もしそうなら、どのようにアプローチしましたか?


1
インターフェイスを実装しているが、特定のメソッドが実装されていないことをドキュメントで明示的に記述している、C#で半一般的に使用されるクラスがあったことを漠然と思い出します。私はそれを見つけることができるかどうかを確認しようとします。
メイジXY

あなたがそれを見つけることができれば、私は間違いなくそれを見て興味があります。
クリスプラット

23
.NETのライブラリでこれの複数のケースを指摘できます- そして、それらはすべて悪いひどいミスとして認識されます。これはリスコフの置換原則の象徴的かつ一般的な違反である-理由のない LSPに違反することが私の答えで見つけることができるここに
ジミー・ホッファ

3
この特定のインターフェイスを実装する必要がありますか、それともスーパーインターフェイスを導入して使用できますか?
クリストファーシュルツ

6
「このクラスに不要なプロパティ」-インターフェースの一部が必要かどうかは、実装者ではなく、インターフェースのクライアント次第です。クラスがインターフェイスのメンバーを合理的に実装できない場合、そのクラスはインターフェイスに適切ではありません。これは、インターフェースの設計が不十分であることを意味する可能性があります-おそらくやりすぎです-しかし、それはクラスを助けません。
セバスチャンレッド

回答:


51

これは、人々がどのようにリスコフ代替原理に違反することを決定するかの古典的な例です。私はそれを強くお勧めしませんが、おそらく別の解決策を奨励します:

  1. おそらく、あなたが書いているクラスは、インターフェースのすべてのメンバーを使用していない場合、インターフェースが規定する機能を提供していません。
  2. あるいは、そのインターフェースは複数のことを行っている可能性があり、インターフェース分離の原則に従って分離することができます。

最初のケースが当てはまる場合は、そのクラスにインターフェースを実装しないでください。これは、実際には接地されないように、接地穴が不要な電気ソケットのように考えてください。あなたは地面に何も差し込まず、大したことはありません!しかし、地面を必要とするものを使用するとすぐに、あなたは壮大な失敗に陥る可能性があります。偽のグラウンドホールを開けない方が良いです。したがって、クラスが実際にインターフェイスが意図したことを実行しない場合は、インターフェイスを実装しないでください。


ウィキペディアの簡単な説明を次に示します。

Liskov Substitution Principleは、「前提条件を強化せず、後条件を弱めない」と簡単に定式化できます。

より正式には、リスコフ置換原理(LSP)は、(強力な)行動サブタイピングと呼ばれるサブタイピング関係の特定の定義であり、最初にBarbara Liskovによって1987年のデータの抽象化と階層という会議の基調講演で導入されました。階層構造の型のセマンティックな相互運用性を保証することを目的としているため、単なる構文上の関係というよりもセマンティックです[...]

同じコントラクトの異なる実装間のセマンティックな相互運用性と代替性のために-あなたはそれらすべてが同じ振る舞いにコミットする必要があります。


インターフェースの分離の原則は、1つの施設だけが必要な場合に、多くの異なることを行うインターフェースを必要としないように、インターフェースを凝集セットに分離する必要があるという考えに基づいています。電気ソケットのインターフェースをもう一度考えてみてください。サーモスタットも使用できますが、電気ソケットの取り付けが難しくなり、非加熱目的での使用が難しくなる場合があります。サーモスタット付きの電気ソケットのように、大きなインターフェイスは実装が難しく使いにくいです。

インターフェイス分離原則(ISP)は、クライアントが使用しないメソッドに依存することを強制するべきではないと述べています。[1] ISPは、非常に大きいインターフェイスをより小さくより具体的なインターフェイスに分割します。これにより、クライアントは、関心のあるメソッドについてのみ知る必要があります。


絶対に。そもそも、これが私にとって正しくないと感じた理由です。時には、愚かなことをしているという穏やかなリマインダーが必要な場合もあります。ありがとう。
クリスプラット

@ChrisPrattそれは本当によくある間違いです。そのため、形式主義は価値があります。コードの匂いを分類することは、それらをより迅速に識別し、以前に使用されたソリューションを思い出すのに役立ちます。
ジミー・ホッファ

4

これがあなたの状況であれば、私にはうまく見えます。

しかし、派生クラスが実際にすべてを実装していない場合、インターフェース(またはその使用)が壊れているように思えます。そのインターフェイスを分割することを検討してください。

免責事項:これを行うには複数の継承が必要であり、C#がそれをサポートしているかどうかはわかりません。


6
C#はクラスの複数の継承をサポートしていませんが、インターフェイスについてはサポートしています。
mgw854

2
あなたは正しいと思います。結局、インターフェイスはコントラクトであり、このプロパティが無効になっている場合、インターフェイスを利用するものを破壊するような方法でこの特定のクラスが使用されないと知っていても、それは明らかではありません。
クリスプラット

@ChrisPratt:うん。
ライトネスレースとモニカ

4

私はこの状況に遭遇しました。実際、他の場所で指摘されているように、BCLにはそのようなインスタンスがあります...私はより良い例を提供し、いくつかの根拠を提供しようとします:

互換性の理由で保管しているインターフェースが既に出荷されている場合...

  • インターフェイスには、廃止された、または廃止されたメンバーが含まれています。例えばBlockingCollection<T>.ICollection.SyncRoot(とりわけ)本来ICollection.SyncRootは時代遅れではありませんが、throwしNotSupportedExceptionます。

  • インターフェースには、オプションとして文書化されているメンバーが含まれており、実装が例外をスローする場合があります。たとえば、MSDNについてIEnumerator.Resetは次のように述べています。

Resetメソッドは、COMの相互運用性のために提供されています。必ずしも実装する必要はありません。代わりに、実装者は単純にNotSupportedExceptionをスローできます。

  • インターフェースの設計の誤りにより、そもそも複数のインターフェースになっていたはずです。コンテナの読み取り専用バージョンを実装するのはBCLの一般的なパターンNotSupportedExceptionです。私は私が作る...それが今期待されているもので、それを自分自身を行っているICollection<T>.IsReadOnlyリターンをtrueあなたがアパートにそれらを伝えることができるようにします。正しい設計では、インターフェイスの読み取り可能なバージョンを使用し、完全なインターフェイスがそれを継承します。

  • 使用するより良いインターフェースはありません。たとえば、インデックスでアイテムにアクセスできるクラスがあり、アイテムが含まれているかどうかを確認し、どのインデックスでサイズがあるかを配列にコピーできます...それは仕事のようですIList<T>が、私のクラスは持っています固定サイズであり、追加も削除もサポートしていないため、リストよりも配列のように機能します。しかし、BCLにはありませんIArray<T>

  • インターフェイスは、複数のプラットフォームに移植されるAPIに属し、特定のプラットフォームの実装では、その一部がサポートされていません。理想的には、それを検出する何らかの方法があるので、そのようなAPIを使用する移植可能なコードは、それらの部分を呼び出すかどうかを決定できます...しかし、それらを呼び出す場合、取得することが完全に適切ですNotSupportedException。これは、これが元の設計では予測されていなかった新しいプラットフォームへの移植である場合に特に当てはまります。


また、なぜサポートされていないのかを検討してください。

時にはInvalidOperationExceptionより良いオプションです。たとえば、クラスにポリモーフィズムを追加するもう1つの方法は、内部インターフェイスのさまざまな実装を使用することです。コードは、クラスのコンストラクターで指定されたパラメーターに応じてインスタンス化するものを選択します。[あなたはオプションのセットが固定されていることを知っていて、サードパーティのクラスは、依存性注入によって導入できるようにしたくない場合、これはparticulary便利です。]私はこれを行っているバックポートThreadLocalの追跡および非追跡の実装であるため、遠すぎるアパート、そしてThreadLocal.Values非追跡実装に何を投げるのでしょうかInvalidOperationExceptionthoであっても、オブジェクトの状態に依存しません。この場合、自分でクラスを紹介しましたが、このメソッドは例外をスローするだけで実装する必要があることを知っていました。

デフォルト値が理にかなっている場合があります。たとえば、ICollection<T>.IsReadOnly上記の場合、場合に応じて「true」または「false」を返すのが理にかなっています。だから...のセマンティクスはIFoo.Bar何ですか?返される適切なデフォルト値があるかもしれません。


補遺:インターフェースを制御している場合(および互換性のためにインターフェースを維持する必要がない場合)、throwする必要がある場合はありませんNotSupportedException。ただし、インターフェイスを2つ以上の小さなインターフェイスに分割して、ケースに適切に合わせる必要があり、極端な状況では「汚染」につながる可能性があります。


0

他の誰かが以前に同様の状況に遭遇したことはありますか?

はい、.Netライブラリデザイナーはそうしました。Dictionaryクラスは、あなたがしていることをします。明示的な実装を使用して、IDictionaryメソッドの一部を効果的に非表示にします。それはのよりよいここで説明したが、辞書の追加、CopyToのを使用するか、またはKeyValuePairsを取る方法を削除するためには、要約するために、あなたは最初のIDictionaryにオブジェクトをキャストする必要があります。

* Microsoftが使用する「非表示」という厳密な意味でのメソッドの「非表示」ではありません。しかし、私はこのインスタンスでより良い用語を知りません。


...ひどいです。
モニカとの軽さのレース

なぜ下票なのですか?
user2023861

2
おそらくあなたが質問に答えなかったからでしょう。Hは。並べ替え。
モニカとの軽さのレース

@LightnessRacesinOrbit、答えを更新してより明確にしました。OPに役立つことを願っています。
user2023861

2
それは私に質問に答えているようです。それが良いか悪い考えかもしれないという事実は、質問の範囲内にあるようには見えませんが、答えを示すのにまだ役に立つかもしれません。
エルセディル

-6

常に実装して、無害な値またはデフォルト値を返すことができます。結局のところ、それは単なるプロパティです。0(intのデフォルトプロパティ)、または実装で意味のある値(int.MinValue、int.MaxValue、1、42など)を返すことができます。

//We don't officially implement this property
int IFoo.Bar
{
     { get; }
}

例外を投げるのは悪い形のようです。


2
どうして?間違ったデータを返すのはどうですか?
ライトネスレースとモニカ

1
これはLSPを破壊します。その理由の説明については、ジミーホファの答えを参照してください。

5
可能な正しい戻り値がない場合は、誤った値を返すよりも例外をスローする方が適切です。何をしても、誤ってこのプロパティを呼び出した場合、プログラムは誤動作します。ただし、例外をスローすると、なぜ誤動作しているのが明らかになります。
タナースウェット

あなたがインターフェースを実装しているときに値がどのように間違っているかは今はわかりません!それはあなたの実装であり、あなたが望むものなら何でも構いません。
ジョンレイナー

コンパイル時に正しい値が不明だった場合はどうなりますか?
アンディ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.