Использование принципа разделения интерфейса

Такая ситуация случалась со мной много раз, и я понятия не имею, как ее решить.

Принцип разделения интерфейса был создан для предотвращения ситуаций, когда некоторые реализации интерфейса не используют его функциональность - это очевидно. Часто возникает ситуация, когда у меня есть список интерфейсов, и я хочу что-то с ними сделать. Давайте посмотрим на пример без 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;}

И я хочу бегать с каждым человеком.

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

Теперь я хочу есть со всеми из них.

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

Теперь, если я хочу использовать ISP, я должен изменить код на:

        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(){};
    }

Как мне составить свой список людей? Должен ли я сделать два списка Runable и Eatable и добавить объекты (некрасиво) или, возможно, второй подход - создать один список и привести их, если это возможно (некрасиво)? Я понятия не имею, что является лучшим соглашением, чтобы сделать это.

Этот пример, возможно, не лучший пример, который я мог себе представить, но я надеюсь, вы понимаете, о чем я.

Отредактировано: я изменил интерфейсы и классы и название принципа.

3 ответа

Решение

Если в вашей системе есть что-то, что работает и ест, определите это как интерфейс:

public interface IHungryRunner : IRunnable, IEatable
{ }

предполагая, что IRunnable и IEatable когда-либо будут использоваться автономно, в противном случае просто держите их вместе.

Вы также можете поддерживать отдельные списки IRunnables и IEatables вместо того, чтобы пытаться объединить их вместе.

Каждое правило, соглашение и шаблон проектирования в архитектуре программного обеспечения должны быть взяты с нуля. Вам нужно подумать о том, почему что-то считается шаблоном правила / соглашения / дизайна, чтобы знать, когда его применять.

Согласно Википедии:

Причина, по которой важно держать класс сосредоточенным на одной проблеме, состоит в том, что это делает класс более устойчивым. Продолжая пример [... создать отчет и распечатать его...], если в процессе компиляции отчета есть изменения, существует большая опасность, что код печати будет нарушен, если он является частью того же класса.

Дело в том, что процесс создания отчета по своей природе не зависит от процесса печати отчета. Таким образом, эти операции должны быть разрешены для изменения отдельно.

В вашем Connect() / Disconnect() Например, я бы предположил, что согласованность этих двух операций настолько велика, что, если одна из них изменяется, весьма вероятно, что другая операция также нуждается в изменении. Таким образом, объединяя Connect() а также Disconnect() в одном интерфейсе не нарушать SRP.

С другой стороны, если операции Eat() а также Run()вам нужно будет учесть взаимосвязь этих двух операций.

В конце: будьте прагматичны! Вы ожидаете расщепления Eat() а также Run() в отдельные интерфейсы, чтобы обеспечить какие-либо преимущества в обозримом будущем? Если нет, вы рискуете нарушить другой принцип дизайна: YAGNI.

Разумно сохранить свой интерфейс IPerson, так как можно утверждать, что бегать и есть - это обе обязанности быть человеком.

Если вы считаете, что есть смысл в определении отдельных интерфейсов IRunnable и IEatable, вы можете использовать их для определения интерфейса IPerson:

public interface IPerson : IRunnable, IEatable {}

Это немного отходит от одного аспекта ответственности в вопросе, но я чувствую, что это может быть частью дилеммы: если у вас есть другой код, который заботится только о "управляемом" аспекте вещей, тогда вы могли бы иметь метод, который принимает IEnumerable<IRunnable> параметр. Через ковариационные правила, представленные в.NET 4 Your List<IPerson> объект является IEnumerable<IRunnable>, так что вы можете просто передать его без кастинга.

void DoSomethingWithRunnables(IEnumerable<IRunnable> runnables)
{
    ...
    foreach (var item in runnables)
    {
        item.Run();
    }
    ...
}
Другие вопросы по тегам