3

I have a software component that is a part of a bigger software product. The software component lives in its namespace Component. Also the component has an interface (some part of it is below) that other components use:

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

What I don't like about the interface is the dependency of some of its methods on internal classes of the component. AddClient() for example depends on the class Component::Client.

Is such dependency acceptable? What are the best practices to remove such dependencies?

9
  • Why is Client an internal class?
    – Q Q
    Dec 30, 2016 at 7:31
  • Because it exists only within the Component and other components don't need it. I considered to make it a class, in general namespace, not Component's. What is the trade-off here?
    – Konstantin
    Dec 30, 2016 at 9:14
  • If other components don't need 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 by Client. Dec 30, 2016 at 9:18
  • That's exactly my question. I need to add clients to Component, but it seems that other components shouldn't know about Client.
    – Konstantin
    Dec 30, 2016 at 9:22
  • Then, your whole abstraction should hide this detail. Maybe, using a Facade
    – Laiv
    Dec 30, 2016 at 10:13

3 Answers 3

4

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::Clients 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::Clients 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.

0
3

AddClient looks more like RegisterClient, where some other class is registering the client with some integer, so that this integer id can then be used as a reference everywhere.

This is good interface. Client is part of Component and the method is accepting only the objects of Component. This is good encapsulation.

If instead of Component::Client some other class would have been there like SomeOtherComponent::Client then there could have been place to raise eyebrows. But the mentioned sample I will say is fine.

1

I guess you can't do much.

Once you (or someone else) has written an interface the same rules as with API's apply: you're basically stuck with it.

Interfaces are not to be designed to be changed because once they're there a whole lot of other code starts relying on it.

If however you want your next new interface to be better designed....but I think that's another question.

1
  • It's a new interface so any changes are allowed!
    – Konstantin
    Dec 30, 2016 at 10:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.