2

This situation happened to me many times and I have no idea how to solve it.

Interface segregation principle was made to prevent situations, when some interface implementations don't use it's functionality - that's obvious. There is often situation that I have a list of interfaces and I want to do something with them. Let's look at example without ISP:

  public interface IPerson
    {
           void Run();
           void Eat();
    }

public class AbcPerson : IPerson
{
       void Run(){};
       void Eat(){};
}

public class XyzPerson : IPerson
{
       void Run(){};
       void Eat(){};
}

List<IPerson> People {get;set;}

And I want to run with every person.

      foreach(var person in People)
      {
         person.Run();
      }

Now I want to eat with all of them.

          foreach(var person in People)
          {
             person.Eat();
          }

Now if I would like to use ISP I should change code to:

        public interface IRunnable
        {
               void Run();
        }
        public interface IEatable
        {
               void Eat();
        }

    public class AbcPerson : IRunnable,IEatable
    {
           void Run(){};
           void Eat(){};
    }

    public class XyzPerson : IRunnable,IEatable
    {
           void Run(){};
           void Eat(){};
    }

How should I make my list of people? Should I make two lists of Runable and Eatable and add objects(ugly) or maybe second approach- create one list and cast them if it's possible(ugly)? I have no idea what is the best convention to do that.

This example maybe is not the best example I could imagine but I hope you know what I mean.

EDITED: I changed interfaces and classes and principle name.

Dave Schweisguth
  • 36,475
  • 10
  • 98
  • 121
MistyK
  • 6,055
  • 2
  • 42
  • 76
  • 2
    I dont think having `Connect` and `Disconnect` in the same interface is violating the SRP. You should describe your interface as something more general than `IConnect`, maybe describing your product, like `IDevice` where they can both live together – Yuval Itzchakov Jun 10 '14 at 13:23
  • Yeah that's why I wrote that it is not a perfect example. It should be something much different, like Eat() and Run(), I will edit it – MistyK Jun 10 '14 at 13:25
  • SRP is the "Do one thing" principle, I don't really see the link with interfaces here. It's not really related to ISP either since you're using all your methods.. I'd say it's more of an OCP case, but that really depends on how the system may change later, which we don't know. – Pierre-Luc Pineault Jun 10 '14 at 13:27
  • Yes are you completely right. I mispelled the name. I meant Interface segregation principle – MistyK Jun 10 '14 at 13:30
  • 1
    You can add a third interface that inherits both. – Silvermind Jun 10 '14 at 13:31
  • Ah, that's it Silvermind. The easiest solutions are the hardest one ;) – MistyK Jun 10 '14 at 13:36

3 Answers3

5

If there is a thing in your system that runs and eats then define it as an interface:

public interface IHungryRunner : IRunnable, IEatable
{ }

assuming that IRunnable and IEatable will ever be used standalone, otherwise just keep them together.

You could also maintain separate lists of IRunnables and IEatables instead of trying to lump them together.

Eric Scherrer
  • 3,328
  • 1
  • 19
  • 34
3

Every rule, convention and design pattern in software architecture should be taken with a grain of salt. You need to think about why something is considered a rule / convention / design pattern in order to know when to apply it.

According to Wikipedia:

The reason it is important to keep a class focused on a single concern is that it makes the class more robust. Continuing with the [...generate a report and print it...] example, if there is a change to the report compilation process, there is greater danger that the printing code will break if it is part of the same class.

The point here is that the process of generating a report is inherently independent from the process of printing the report. Thus, these operations should be allowed to change separately.

In your Connect() / Disconnect() example I would assume that the cohesion of these two operations is so great that if one changes, it is very likely that the other operation needs to change as well. Thus, combining Connect() and Disconnect() in one interface doesn't violate SRP.

On the other hand, if the operations are Eat() and Run(), you will need to consider the cohesion of these two operations.

In the end: be pragmatic! Do you expect splitting Eat() and Run() into separate interfaces to provide any benefits for the foreseeable future? If not, you risk violating another design principle: YAGNI.

Rune
  • 8,340
  • 3
  • 34
  • 47
  • Additionally, refactoring from `IPerson` with the members directly to `IPerson : IEatable, IRunnable` is painless because none of the classes referencing `IPerson` will have to change – Ben Aaronson Jun 10 '14 at 13:39
2

It's reasonable to keep your IPerson interface, as one could argue that running and eating are both responsibilities of "being a person".

If you felt there was value in defining separate IRunnable and IEatable interfaces, then you could use them to define your IPerson interface:

public interface IPerson : IRunnable, IEatable {}

This is getting away from the single responsibility aspect of the question a bit, but I'm sensing that this might be part of the dilemma: if you have other code that only cares about the "runnable" aspect of things, then you could have a method that takes an IEnumerable<IRunnable> parameter. Through the covariance rules introduced in .NET 4 Your List<IPerson> object is an IEnumerable<IRunnable>, so you can simply pass it without any casting.

void DoSomethingWithRunnables(IEnumerable<IRunnable> runnables)
{
    ...
    foreach (var item in runnables)
    {
        item.Run();
    }
    ...
}
Dr. Wily's Apprentice
  • 10,212
  • 1
  • 25
  • 27