より大きなソフトウェア製品の一部であるソフトウェアコンポーネントを持っています。ソフトウェアコンポーネントは、その名前空間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
_に依存します。
そのような依存関係は受け入れられますか?そのような依存関係を削除するためのベストプラクティスは何ですか?
ここに私が見る4つの可能性があります:
Component::Client
の公開が実装の詳細の多くをリークしない限り、それは大した問題ではありません。これが公開したいインターフェースのタイプであると感じ、このインターフェースを無効にするような劇的な変更を期待していない場合、それは単にNo Big Deal™である可能性があります。
Component::Client
sはほとんどがデータ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
インスタンスの作成が特に複雑な場合は、ビルダーなどのパターンを必要に応じて曲げる方法があります。)
Component::Client
sは主に動作です動作を提供するためにComponent::Client
インスタンスをIComponent
インスタンスに渡す場合、Component::Client
の公開は回避できない場合があります。これを戦略オブジェクトとして使用することは、たとえば動作を提供する形式です。動作が提供される方法は2つあります。
Component::Client
を作成しますクライアントコードから選択して渡す必要があるComponent::Client
の実装がいくつかある場合は、代わりにそのクライアントコードに列挙型(または同様のもの)を渡すことができます。 enumは、内部で作成および格納するComponent::Client
実装のタイプをAddClient
メソッドに通知します。クラスは公開されていませんが、代わりに列挙型が公開されています。
Component::Client
を拡張してカスタム実装を提供しますここでできることはそれほど多くありませんが、Component::Client
を公開することです。それを拡張するためにクライアントコードはそれを見る必要があります。それ以外の場合は非常に大規模な内部実装に少しだけ追加の動作が必要な場合は、クライアントがComponent::Client
の大部分を実装および非表示にするためのはるかに単純な戦略インターフェイスを公開できます。必要に応じて、既存のインターフェイスを(標準ライブラリまたは独自の場所に)再利用することもできます。
動作とデータの両方を提供するComponent::Client
がある場合、2つの方法でそれを見ることができます。最初に、それはそれで意味をなさないかもしれません、そしてあなたはそれを放っておくべきです(別名、可能性1)。または、動作とデータを分解して、新しく分割したパーツで可能性2と可能性3を実行することもできます。これを行うときは、単一の責任を念頭に置いてください。クラスには、多かれ少なかれ1つの責任が必要です。
最後に、すべてのケースで、コンポーネントクライアントのideaがクライアントコードにまだ存在していることを指摘しておきます。多くの場合、データ値または動作値オブジェクトのみであっても、それを表すクラスまたはタイプがあると有益です。
AddClientはRegisterClientに似ています。他のクラスがクライアントを整数に登録しているため、この整数IDはどこでも参照として使用できます。
これは良いインターフェースです。クライアントはコンポーネントの一部であり、メソッドはコンポーネントのオブジェクトのみを受け入れます。これは良いカプセル化です。
Component :: Clientの代わりに、SomeOtherComponent :: Clientのような他のクラスがあったとしたら、眉を上げる場所があったでしょう。しかし、私が言う前述のサンプルは問題ありません。
あなたは多くを行うことができないと思います。
あなた(または他の誰か)がインターフェースを作成すると、APIの適用と同じルールが適用されます。基本的にはそれで行き詰まります。
インターフェイスが変更されるように設計することはできません。インターフェイスが存在すると、他の多くのコードがそれに依存し始めるためです。
しかし、もしあなたがあなたの次の新しいインターフェースをより良く設計したいなら……それは別の問題だと思います。