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