とても簡単です。私はインターフェイスを実装していますが、このクラスには不要なプロパティが1つあり、実際には使用しないでください。私の最初のアイデアは、次のようなことをすることでした。
int IFoo.Bar
{
get { raise new NotImplementedException(); }
}
これ自体に問題はないと思いますが、「正しい」とは感じません。他の誰かが以前に同様の状況に遭遇したことはありますか?もしそうなら、どのようにアプローチしましたか?
とても簡単です。私はインターフェイスを実装していますが、このクラスには不要なプロパティが1つあり、実際には使用しないでください。私の最初のアイデアは、次のようなことをすることでした。
int IFoo.Bar
{
get { raise new NotImplementedException(); }
}
これ自体に問題はないと思いますが、「正しい」とは感じません。他の誰かが以前に同様の状況に遭遇したことはありますか?もしそうなら、どのようにアプローチしましたか?
回答:
これは、人々がどのようにリスコフ代替原理に違反することを決定するかの古典的な例です。私はそれを強くお勧めしませんが、おそらく別の解決策を奨励します:
最初のケースが当てはまる場合は、そのクラスにインターフェースを実装しないでください。これは、実際には接地されないように、接地穴が不要な電気ソケットのように考えてください。あなたは地面に何も差し込まず、大したことはありません!しかし、地面を必要とするものを使用するとすぐに、あなたは壮大な失敗に陥る可能性があります。偽のグラウンドホールを開けない方が良いです。したがって、クラスが実際にインターフェイスが意図したことを実行しない場合は、インターフェイスを実装しないでください。
ウィキペディアの簡単な説明を次に示します。
Liskov Substitution Principleは、「前提条件を強化せず、後条件を弱めない」と簡単に定式化できます。
より正式には、リスコフ置換原理(LSP)は、(強力な)行動サブタイピングと呼ばれるサブタイピング関係の特定の定義であり、最初にBarbara Liskovによって1987年のデータの抽象化と階層という会議の基調講演で導入されました。階層構造の型のセマンティックな相互運用性を保証することを目的としているため、単なる構文上の関係というよりもセマンティックです[...]
同じコントラクトの異なる実装間のセマンティックな相互運用性と代替性のために-あなたはそれらすべてが同じ振る舞いにコミットする必要があります。
インターフェースの分離の原則は、1つの施設だけが必要な場合に、多くの異なることを行うインターフェースを必要としないように、インターフェースを凝集セットに分離する必要があるという考えに基づいています。電気ソケットのインターフェースをもう一度考えてみてください。サーモスタットも使用できますが、電気ソケットの取り付けが難しくなり、非加熱目的での使用が難しくなる場合があります。サーモスタット付きの電気ソケットのように、大きなインターフェイスは実装が難しく使いにくいです。
インターフェイス分離原則(ISP)は、クライアントが使用しないメソッドに依存することを強制するべきではないと述べています。[1] ISPは、非常に大きいインターフェイスをより小さくより具体的なインターフェイスに分割します。これにより、クライアントは、関心のあるメソッドについてのみ知る必要があります。
これがあなたの状況であれば、私にはうまく見えます。
しかし、派生クラスが実際にすべてを実装していない場合、インターフェース(またはその使用)が壊れているように思えます。そのインターフェイスを分割することを検討してください。
免責事項:これを行うには複数の継承が必要であり、C#がそれをサポートしているかどうかはわかりません。
私はこの状況に遭遇しました。実際、他の場所で指摘されているように、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
非追跡実装に何を投げるのでしょうかInvalidOperationException
thoであっても、オブジェクトの状態に依存しません。この場合、自分でクラスを紹介しましたが、このメソッドは例外をスローするだけで実装する必要があることを知っていました。
デフォルト値が理にかなっている場合があります。たとえば、ICollection<T>.IsReadOnly
上記の場合、場合に応じて「true」または「false」を返すのが理にかなっています。だから...のセマンティクスはIFoo.Bar
何ですか?返される適切なデフォルト値があるかもしれません。
補遺:インターフェースを制御している場合(および互換性のためにインターフェースを維持する必要がない場合)、throwする必要がある場合はありませんNotSupportedException
。ただし、インターフェイスを2つ以上の小さなインターフェイスに分割して、ケースに適切に合わせる必要があり、極端な状況では「汚染」につながる可能性があります。
他の誰かが以前に同様の状況に遭遇したことはありますか?
はい、.Netライブラリデザイナーはそうしました。Dictionaryクラスは、あなたがしていることをします。明示的な実装を使用して、IDictionaryメソッドの一部を効果的に非表示にします。それはのよりよいここで説明したが、辞書の追加、CopyToのを使用するか、またはKeyValuePairsを取る方法を削除するためには、要約するために、あなたは最初のIDictionaryにオブジェクトをキャストする必要があります。
* Microsoftが使用する「非表示」という厳密な意味でのメソッドの「非表示」ではありません。しかし、私はこのインスタンスでより良い用語を知りません。
常に実装して、無害な値またはデフォルト値を返すことができます。結局のところ、それは単なるプロパティです。0(intのデフォルトプロパティ)、または実装で意味のある値(int.MinValue、int.MaxValue、1、42など)を返すことができます。
//We don't officially implement this property
int IFoo.Bar
{
{ get; }
}
例外を投げるのは悪い形のようです。