3

My understanding of ISP is that the "client" in the definition (see below) can mean either a class that implements the interface or an instance declared with 1 of the various interface options, based on client mentioned in Wikipedia definition:

The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use. [source]

I've seen some SE posts that say it is when the instance is used, and others saying when the interface is implemented. According to this SE answer it can be considered both. So I'm going to continue onto the question with the assumption in mind that it can be either.

Let's say that for the particular architecture I am building it is going to have 1 class implementing all the interfaces, but I want the the instance to be looked at in a flexible way when the instance is used.

public interface IAutoToggle
{
    IEnumerable<IToggleable> Toggle(IAttribute attribute);
}

public interface IManualToggle
{
    void Toggle(IEnumerable<IToggleable> toggles);
}

public interface IMixedToggle: IAutoToggle, IManualToggle {}

public class Toggler : IMixedToggle
{
    //...implementations
}

Now, when instantiated it can be decided whether to do 1 of the 2 or both:

IMixedToggler mixedToggler = new Toggler();
//or
IAutoToggler autoToggler = new Toggler();
//or
IManual manualToggler = new Toggler();

Does this way of doing things still follow the Interface-segregation principle?

If it does, is there a way to do this same effect in C# without having that extra inherited interface? For example Objective-C, being a more dynamic language allows you to do something like this if I'm not mistaken:

id<IAutoToggle, IManualToggle> toggler = [[Toggler alloc] init];

Is there a way to avoid the extra IMixedToggle interface in C# (like done above in Objective-C)? Or are there good reasons to not go down the path I'm doing?

  • 1
    I'll just leave this here as an example. It's way more complex than your example, and it's well documented and used. I'd say you're following ISP pretty well. – Machado Apr 10 '17 at 16:45
  • If implemenration of AutoToggle and ManualToggle not related to each other, then this class will violate Single Responsibility Principal. What prevents you from using two instances instead of one. With one instance line mixerToggler.Toggle can be confusing because will be difficult to recognise which implementation is called – Fabio Apr 10 '17 at 20:27
  • @Fabio, the primary concern of the class is to toggle so I think it still follows SRP, with high cohesion around toggling. The void Toggle(toggleables) takes in specific things to toggle while the IEnumerable<Toggleables> Toggle(attribute) takes in an attribute and returns what got toggled based on the toggleables containing such attribute. It might be confusing since I tried to generalize the code from a concrete example. I'm using this design for a "Guess Who" game where characters can be toggled manually/individually or automatically based on what characteristics they have. – morbidhawk Apr 11 '17 at 12:21
5

Does this way of doing things still follow the Interface-segregation principle?

This is fine and does not break the ISP.

is there a way to do this same effect in C# without having that extra inherited interface?

You don't need it. Instead of

public interface IMixedToggle: IAutoToggle, IManualToggle {}

public class Toggler : IMixedToggle
{
    //...implementations
}

you can just write

public class Toggler : IAutoToggle, IManualToggle
{
    //...implementations
}

When you use the class, depending on which interface you need, you would instantiate it as

IAutoToggle o = container.Resolve<IAutoToggle>();

or

IManualToggle o = container.Resolve<IManualToggle>();

Depending on how your factory is configured, it is quite possible the factory will return the same concrete instance to both calls. But it doesn't have to, because we are following the ISP.

Now if you have a piece of code that actually depends on the fact that the same object implements both interfaces at the same time... well... that would violate the ISP. The factory should be able to supply any old object that implements the interface without impacting the calling code.

  • That works for the class declaration, but what about the instance declaration? In this case there is a scenario where I'd need an instance that has both a manual way of toggling and an automatic way of toggling. In the code above the only way would be to use the instance declared with the class type. Toggler toggler = new Toggler(); which means I wouldn't be able to use the interface declaration, that might be a tradeoff I'd have to make. – morbidhawk Apr 10 '17 at 17:06
  • 1
    Not being a C# expert, I'd say I never saw something like that in code. But I'm finding difficult to find a use case where you have to explicitly declare you want an object implementing 2 different interfaces, and this combination isn't a "real" third object. Can you provide a more concrete example then the Toggle one ? – Machado Apr 10 '17 at 17:25
  • you could pass in both interfaces as separate variables. this is more flexible – Ewan Apr 10 '17 at 17:28
  • 1
    I've edited my answer to include instance declaration. – John Wu Apr 10 '17 at 17:50
  • 1
    @morbidhawk I completely agree. – John Wu Apr 10 '17 at 18:54
0

The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use

I think definition above mean that

no client should be forced to implement methods it does not use

It is ok for interface's consumer to depend on interface with multiple methods but uses only one of them.

So in your case you can introduce only one interface

public interface IToggler
{
    IEnumerable<IToggleable> ToggleByAttribute(IAttribute attribute);

    void Toggle(IEnumerable<IToggleable> toggles);
}

And implementation

public interface Toggler : IToggler
{
    IEnumerable<IToggleable> ToggleByAttribute(IAttribute attribute) 
    { 
        // Implementation
    }

    void Toggle(IEnumerable<IToggleable> toggles)
    { 
        // Implementation
    }
}

You can consider applying Interface Segregation Principle only in cases when you decide to have more then one implementation for one of the methods.

  • It is ok for interface's consumer to depend on interface with multiple methods but uses only one of them. Thanks for giving a different perspective about this. I think that ISP is a guiding principle and that ideally it is preferable to only have the methods that you will call, but also need to consider that code gets refactored. ISP taken to an extreme is the IEnumerable interface, it can only retrieve GetEnumerator() that enumerates the collection. If you later realize you might also need quick access to the count you'll have to change the interface to be IReadOnlyCollection instead. – morbidhawk Apr 24 '17 at 12:32
  • Continued from my last comment, I would argue that IReadOnlyCollection and IEnumerable are both good choices to use if you know you'll never need to modify the collection. So I can also see the other point of view that being further segregated gives code intent. But also it is preferable to have all the methods that you might end up needing. I guess it might depend on the context. – morbidhawk Apr 24 '17 at 12:34
  • 1
    @morbidhawk, consider IRepository interface with methods Get, Update and Insert methods. They are related to each other, for example if you change name of the table you need update all three methods. But I can have classes which uses only Get method. If I introduce own interface IGetRepository then "I inform others/developers/consumers" that Get method can be implemented without Update or Insert methods. Which be misleading for maintaining repository code. My point that your two methods are related to each other - if you change "Toggling" in one you will need to change another – Fabio Apr 24 '17 at 16:19
0

I received great feedback from the other answers at the time I posted this question. I was reminded of this scenario again today and I thought of a couple specific new ways that this can be handled, and I'm realizing now it appears to be very much in line with the answers I received before. To explain this I need to make a distinction between the implementor and the instantiator.

Instantiator (external code) vs Implementor (internal code)

I'll refer to the implementor of the Toggler class as implementor and the code that uses it and at some point instantiates it as instantiator.

The Toggler class implementation should not be overly-concerned about all the combinations of ways it can be used by the code that'll instantiate it. It should just do what makes sense in regards to it's own class implementation. It's up to the instantiator to decide how it'll be used. The implementor should probably be aware what it's own interface looks like and that it should be clean/simple, and as such it knows how it can be used but it isn't responsible for all the ways it will be used.

Option 1: If I separate interfaces

Let's say it is implemented as such:

public class Toggler : IAutoToggler, IManualToggler { ... }

Now at this point we can already easily do IAutoToggler autoToggler = new Toggler(); to only program to the Auto toggling interface and same for manual toggling. But the concern now is what if I want both? This responsibility falls to the instantiator. As mentioned in John Wu's answer this can be handled by an IoC container and with dependency injection one way could be to inject both interfaces into one classes constructor and use the interfaces separately in a single class that wraps it.

public class TogglerHandler
{
    private readonly IAutoToggler _autoToggler;
    private readonly IManualToggler _ toggler;
    public TogglerHandler(IAutoToggler autoToggler, IManualToggler toggler) {...}

    // exposing access to both underlying interfaces here:
    public IEnumerable<IToggleable> Toggle(IAttribute attribute) {...}
    public void Toggle(IEnumerable<IToggleable> toggles) {...}
}

Another way to go about this is to subclass the Toggler class and provide your own interface:

public interface IMixedToggler : IAutoToggler, IManualToggler {}
public class MixedToggler : Toggler, IMixedToggler 
{
}

// somewhere using the code now can use a mixed toggling interface
IMixedToggler toggler = new MixedToggler();

Option 2: If I make it a single interface

Fabio mentioned that it makes sense here to use a single interface.

Let's say the implementation is like this instead and has the action methods previously in a single interface:

public interface IToggler
{
    IEnumerable<IToggleable> ToggleByAttribute(IAttribute attribute);
    void Toggle(IEnumerable<IToggleable> toggles);
}
public class Toggler : IToggler { ... }

Now the instantiator or user of this interface might not need the full interface. This responsibility to expose only what is needed falls on the instantiator that will use the IToggler implementation. One possible way is to wrap it again, so let's say only the auto-toggling is needed:

public interface IAutoToggler
{
    IEnumerable<IToggleable> Toggle(IAttribute attribute)
}

public class AutoToggler : Toggler, IAutoToggler 
{
    ...
}

// somewhere using the code now can only use auto-toggler interface
IAutoToggler autoToggler = new AutoToggler();

Conclusion

What I've concluded is that no matter whether I separated the interface or kept it a single one, it is still possible to expose or restrict access to it via inheritance or by wrapping the code. The reason I had a hard time seeing this before is because I was both the one who implements that class and also the one who instantiates it but perhaps making this role distinction is useful in making these kinds of decisions.

When designing the interface of an implementation do it such that it makes the most sense for the internal implementation point of view, and for external code calling it either use it as is if it gives you access to what you need or change it to a form that makes more sense to interact with.

In answer to my original question, the IMixedToggler interface should not have been created/implemented. It's only purpose would be to have an additional way of interfacing with the class. That should have been figured out by the code the instantiates/uses it as the responsible fell over there in external code.

Side Note

This code excerpt is from from Kestrel's Http Parser:

public class HttpParser<TRequestHandler> 
    : IHttpParser<TRequestHandler> 
    where TRequestHandler 
        : IHttpHeadersHandler, 
          IHttpRequestLineHandler
{
   ...
}

This example actually led me to conclude that internal implementation should not dictate how it's going to be used. The HttpParseObject cannot be initialized without there being an interface existing that are both IHttpHeadersHandler and IHttpRequestLineHandler. So to even initialize this thing you need to have an interface that isn't even specified directly here, it only says it needs an interface that inherits from both of those. So essentially it is enforcing that the instantiator create a new interface that conforms to what it wants:

interface IFoo: IHttpHeadersHandler, IHttpRequestLineHandler { }
var parser = new HttpParser<IFoo>(true);
  • So, you should really try to separate the "Interface" in ISP from the c# Interface keyword. If you have a client that needs both implementations, it's totally okay to rely on the interface that the concrete implementation provides. If you feel the need for further decoupling, then go with the IMixedToggle version. As for this particular idea, you'd be better off implementing "As" methods that like so return (IManualToggle)this; – RubberDuck Jul 22 '17 at 16:19
  • 1
    @RubberDuck good point, classes already have public/internal interfaces based on what access modifier is used on the methods/properties. The example I showed with MixedToggler class that merely inherited from Toggler was unnecessary altogether if I use the class directly. – morbidhawk Jul 22 '17 at 16:44

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.