web-dev-qa-db-ja.com

インターフェイスの内部クラスへの依存関係を削除する方法

より大きなソフトウェア製品の一部であるソフトウェアコンポーネントを持っています。ソフトウェアコンポーネントは、その名前空間Componentにあります。また、コンポーネントには、他のコンポーネントが使用するインターフェース(その一部は以下にあります)があります。

_class IComponent {
    ...
    virtual void AddClient(unsigned int id, Component::Client client) = 0;
    virtual void RemoveClient(unsigned int id) = 0;
    virtual void PrintClients() = 0;
    ...
};
_

インターフェースについて私が気に入らないのは、コンポーネントの内部クラスに対するそのメソッドの一部の依存性です。 AddClient()は、たとえば、クラス_Component::Client_に依存します。

そのような依存関係は受け入れられますか?そのような依存関係を削除するためのベストプラクティスは何ですか?

3
Konstantin

ここに私が見る4つの可能性があります:

可能性1- [〜#〜] nbd [〜#〜]

Component::Clientの公開が実装の詳細の多くをリークしない限り、それは大した問題ではありません。これが公開したいインターフェースのタイプであると感じ、このインターフェースを無効にするような劇的な変更を期待していない場合、それは単にNo Big Deal™である可能性があります。

可能性2-Component::Clientsはほとんどがデータ

Component::Clientの外部インターフェースがほとんどがデータの場合、直接要求するのではなく、作成する必要のあるデータパーツを要求し、代わりに内部的に作成するだけです。 Component::Clientの修正された内部実装が1つまたはいくつかしかない場合は、この状況にある可能性があります。したがって、呼び出しコードが次のようになる場合:

Component::Client client(data, filters, listeners, etc);
component.AddClient(11, client);
// client is never used again

次に、呼び出しコードに関する限り、クライアントはコンポーネントに渡される単なるデータストアです。クライアントに負荷がかかっている場合でも、コンポーネントにとって興味深いだけであれば、外部的には関係がないので、メソッドを次のように変更できます。

component.AddClient(11, data, filters, listeners, etc);
// What client?

AddClientメソッドはComponent::Clientインスタンスを内部的に作成して保存しますが、外部コードはそれを知ることはありません。 (Component::Clientインスタンスの作成が特に複雑な場合は、ビルダーなどのパターンを必要に応じて曲げる方法があります。)

可能性3-Component::Clientsは主に動作です

動作を提供するためにComponent::ClientインスタンスをIComponentインスタンスに渡す場合、Component::Clientの公開は回避できない場合があります。これを戦略オブジェクトとして使用することは、たとえば動作を提供する形式です。動作が提供される方法は2つあります。

可能性3、パート1-クライアントコードは、固定数の提供された実装からComponent::Clientを作成します

クライアントコードから選択して渡す必要があるComponent::Clientの実装がいくつかある場合は、代わりにそのクライアントコードに列挙型(または同様のもの)を渡すことができます。 enumは、内部で作成および格納するComponent::Client実装のタイプをAddClientメソッドに通知します。クラスは公開されていませんが、代わりに列挙型が公開されています。

可能性3、パート2-クライアントコードはComponent::Clientを拡張してカスタム実装を提供します

ここでできることはそれほど多くありませんが、Component::Clientを公開することです。それを拡張するためにクライアントコードはそれを見る必要があります。それ以外の場合は非常に大規模な内部実装に少しだけ追加の動作が必要な場合は、クライアントがComponent::Clientの大部分を実装および非表示にするためのはるかに単純な戦略インターフェイスを公開できます。必要に応じて、既存のインターフェイスを(標準ライブラリまたは独自の場所に)再利用することもできます。

可能性4-可能性2 +可能性3

動作とデータの両方を提供するComponent::Clientがある場合、2つの方法でそれを見ることができます。最初に、それはそれで意味をなさないかもしれません、そしてあなたはそれを放っておくべきです(別名、可能性1)。または、動作とデータを分解して、新しく分割したパーツで可能性2と可能性3を実行することもできます。これを行うときは、単一​​の責任を念頭に置いてください。クラスには、多かれ少なかれ1つの責任が必要です。


最後に、すべてのケースで、コンポーネントクライアントのideaがクライアントコードにまだ存在していることを指摘しておきます。多くの場合、データ値または動作値オブジェクトのみであっても、それを表すクラスまたはタイプがあると有益です。

4
cbojar

AddClientはRegisterClientに似ています。他のクラスがクライアントを整数に登録しているため、この整数IDはどこでも参照として使用できます。

これは良いインターフェースです。クライアントはコンポーネントの一部であり、メソッドはコンポーネントのオブジェクトのみを受け入れます。これは良いカプセル化です。

Component :: Clientの代わりに、SomeOtherComponent :: Clientのような他のクラスがあったとしたら、眉を上げる場所があったでしょう。しかし、私が言う前述のサンプルは問題ありません。

3
Manoj R

あなたは多くを行うことができないと思います。

あなた(または他の誰か)がインターフェースを作成すると、APIの適用と同じルールが適用されます。基本的にはそれで行き詰まります。

インターフェイスが変更されるように設計することはできません。インターフェイスが存在すると、他の多くのコードがそれに依存し始めるためです。

しかし、もしあなたがあなたの次の新しいインターフェースをより良く設計したいなら……それは別の問題だと思います。

1
Pieter B