There are four possibilities I see here:
Possibility 1 - NBD
It's not a big deal so long as exposing Component::Client
does not leak much of the implementation details. If you feel like this is the type of interface you want to expose and you are not expecting to have any dramatic changes that would invalidate this interface, then it might just be No Big Deal™.
Possibility 2 - Component::Client
s are mostly data
If the external interface of a Component::Client
is mostly data, then instead of asking for one directly, just ask for the data parts you need to make one, and just make it internally instead. If there is only one or a few fixed internal implementations of Component::Client
, you might be in this situation. So if your calling code looks like:
Component::Client client(data, filters, listeners, etc);
component.AddClient(11, client);
// client is never used again
Then as far as the calling code is concerned, the client is just a data store being passed to the component. Even though the client may have a load of behavior on it, if it is only interesting to the component, then externally it is irrelevant, and you could change the method to be:
component.AddClient(11, data, filters, listeners, etc);
// What client?
The AddClient
method would internally create and store the Component::Client
instance, but external code would never know about it. (If creating a Component::Client
instance is particularly complicated, there are ways to bend patterns like a builder to what you might need.)
Possibility 3 - Component::Client
s are mostly behavior
If you are passing in a Component::Client
instance to an IComponent
instance mostly to provide behavior, then exposing the Component::Client
might not be avoidable. Using it as a strategy object is a form of providing behavior, for instance. There are two ways that behavior might be being provided.
Possibility 3, Part 1 - Client code creates a Component::Client
from a fixed number of provided implementations
If there are a few implementations of Component::Client
that your client code must choose from and then pass in, you could instead have that client code pass in an enum (or similar). The enum would tell the AddClient
method which type of Component::Client
implementation to create and store internally. Note that though the class is not exposed, an enum is exposed instead.
Possibility 3, Part 2 - Client code extends Component::Client
to provide custom implementations
There's not a whole lot here you can do but to expose Component::Client
. Client code needs to see it to extend it. If you only need a little bit of additional behavior for an otherwise very large internal implementation, you could expose a much simpler strategy interface for clients to implement and hide the bulk of the Component::Client
. You might even be able to repurpose a pre-existing interface (in the standard library or elsewhere in your own) to meet your needs.
Possibility 4 - Possibility 2 + Possibility 3
If you have a Component::Client
that is providing both behavior and data, then you can look at it two ways. First, it could just make sense then, and you should leave it alone (aka, Possibility 1). Or, you could break apart the behavior and the data, then do Possibility 2 and Possibility 3 with the newly split parts. Keep in mind single responsibility as you do this: A class should have a single responsibility, not more and not less.
As a final note, I want to point out that in all cases, the idea of a component client still exists to the client code. Often, having a class or type to represent that is beneficial, even if only a data value or behavioral value object.
Client
an internal class?Component
and other components don't need it. I considered to make it a class, in general namespace, notComponent
's. What is the trade-off here?Client
, why do you have an interface that takes it as an argument? That suggests there is at least one other component that needs/uses the concept represented byClient
.Component
, but it seems that other components shouldn't know aboutClient
.