1

I was refactoring an application to respect SOLID principles. When I was applying interface segregation I found the following case:

interface A
{ 
    void methodA();
    void methodA2();
}

interface B
{ 
    void methodB();
    void methodB2();
}

interface C : A, B
{ 
    void methodC();
}

interface D
{ 
    void methodD();
    void methodD2();
    void methodD3();
    //Same signature and semantics as in interface C
    void methodC();

}

Since methodC is the same in interface C and D but interface D doesn't need the methods from A and B, I extracted methodC to another interface, so the design would be.

interface A
{ 
    void methodA();
    void methodA2();
}

interface B
{ 
    void methodB();
    void methodB2();
}

interface C : A, B, E
{ 
}

interface D : E
{ 
    void methodD();
    void methodD2();
    void methodD3();
}

interface E
{
    void methodC();
}

Now I have and interface with only inherited methods and no methods of its own. This means I don't have to refactor the clients that depend on interface C to segregate it.

Is this a good design considering the refactored interface C does not declare methods of its own but only serves as kind of an inheritance container? Should I instead refactor the clients that depend of interface C and add direct dependencies to interface A, B and E? In practice this has not much of a code impact as there is only one client, but is this conceptually sound?

Also, would this be a good design choice to avoid having too many direct dependencies (constructor parameters if using constructor dependency injection, for instance) that are semantically correlated?

  • 2
    I'm generally cautious about using interface inheritance. Does interface C have a meaning in your domain? Is there a reason for a class to implement C instead of implementing A, B, and E, like a consumer that uses methodA and methodC on a variety of things that implement all three interfaces? – Andrew Piliser Nov 20 '18 at 20:57
  • In my domain it makes sense to always implement all three interfaces if you need interface C.As a matter of fact, interface C is the actual domain object while interface D is just to allow mocking since its implementations wrap access to hardware devices. – Cesar Hernandez Nov 20 '18 at 21:09
  • And what is interface E? And D for that matter - "it's to allow mocking" doesn't really describe what it is. – immibis Nov 20 '18 at 22:27
  • Interface D is the contract for accessing a physical device facilites. Interface E is for querying device status. Interface B is IDisposable and interface A is for detection of systematic eventos when device un USB ports are plugged/unplugged. – Cesar Hernandez Nov 20 '18 at 23:43
  • Whether methodC in the two interfaces is one and the same is highly contextual and not answerable as a general rule. Assuming they must be the same because they look the same is the equivalent of ensuring that two classes that both have a Name property must inherit this property from a shared based class (or implement it from a shared interface). While that may be correct in some cases, it is also wrong in other cases. You might get a better answer to your question if you specify a context. – Flater Nov 21 '18 at 7:50
3

Interfaces are about a contract. The consumer, and the producer of these interfaces don't care about each other, just that they follow the same operational protocol, and provide the same semantics.

Scenario

  1. There are no consumers for interfaces: A, B, or E. Only consumers for C.
  2. There are consumers for each interface: A, B, and E. Only that there is an implementation that provides all three.
  3. Interface C changes the protocol/semantics by integrating, or refining the relationship between A, B, and E.

Under scenario 1, because there are no consumers for that specific service, the software has no need of it. Unless there is some other reason not iterated in the question, collapse those definitions into C. Even maintaining these sub-interfaces for "testing" is likely to become counter-productive.

Under Scenario 2, because there is no consumer for C, the system has no need of it, it is a syntactic convenience alone. Remove it, and make the implementation inherit from interfaces A,B, and E directly. It is making it harder to inject alternate behaviour into the code, and is probably complicating many of your unit tests.

Under Scenario 3, take a really long hard look at the operation protocol, and semantic guarantees offered by the sub-interfaces. Does the derived interface C expand that logic in a substitutable manner? Would a consumer using the sub-interfaces, say A, become surprised when an unrelated service, say B, has changed in an illogical manner?

If it really is consistent, then having it extend A, B, and E communicates the extended semantics, make sure it is obvious though through descriptive unit tests/documentation.

If it is not consistent, do not extend those interfaces, instead copy their definitions locally. They should be treated differently, because while the method names look similar they have different semantics, and/or operational patterns.

interface C
{
    void methodA();
    void methodA2();
    void methodB();
    void methodB2();
    void methodC();
}

Apply similar logic for interface D.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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