web-dev-qa-db-ja.com

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

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

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

これ自体は何も問題はないと思いますが、「正しい」とは思われません。他の誰かが以前に同じような状況に遭遇したことがありますか?もしそうなら、あなたはそれにどのように取り組みましたか?

31
Chris Pratt

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

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

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


ここにウィキペディアからのいくつかの簡単なビットがあります:

リスコフ置換の原則 は、「前提条件を強化せず、事後条件を弱めない」として簡単に定式化できます。

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

同じコントラクトの異なる実装間のセマンティックな相互運用性と代替可能性については、同じ動作にコミットするためにそれらすべてが必要です。


インターフェイス分離の原則 は、インターフェイスをまとまりのあるセットに分離して、必要なときに多くの異なる処理を実行するインターフェイスを必要としないようにする必要があるという考え方を示していますone機能。電気ソケットのインターフェースについてもう一度考えてみてくださいcouldサーモスタットもありますが、電気ソケットの取り付けが難しくなり、非暖房目的での使用が難しくなる可能性があります。サーモスタット付きの電気ソケットのように、大きなインターフェースは実装や使用が困難です。

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

53
Jimmy Hoffa

これがあなたの状況であれば、私には問題ありません。

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

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

私はこの状況に遭遇しました。実際に指摘したように elsewhere BCLにはそのようなインスタンスがあります...私はより良い例を提供し、いくつかの理論的根拠を提供しようとします:

互換性の理由で維持している出荷済みのインターフェースがある場合...

  • インターフェースには、廃止または破棄されたメンバーが含まれています。たとえば、BlockingCollection<T>.ICollection.SyncRootは(特に)ICollection.SyncRoot自体は廃止されていませんが、NotSupportedExceptionがスローされます。

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

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

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

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

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


また、サポートされていない理由も考慮してください。

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

デフォルト値が意味をなす場合があります。たとえば、上記のICollection<T>.IsReadOnlyの場合、ケースに応じて単に「true」または「false」を返すことは理にかなっています。それで... IFoo.Barのセマンティクスは何ですか?返すべき実用的なデフォルト値があるかもしれません。


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

4
Theraot

他の誰かが以前に同じような状況に遭遇したことがありますか?

はい、.Netライブラリデザイナーはそうしました。辞書クラスはあなたがやっていることをします。明示的な実装を使用して、いくつかのIDictionaryメソッドを効果的にhide*します。これは ここで説明します ですが、要約すると、KeyValuePairを受け取るディクショナリのAdd、CopyTo、またはRemoveメソッドを使用するには、まずオブジェクトをIDictionaryにキャストする必要があります。

*Wordの厳密な意味でのメソッドの「非表示」ではありません Microsoftが使用 。しかし、私はこの例でより良い用語を知りません。

0
user2023861