Должны ли интерфейсы расширяться (и при этом наследовать методы) других интерфейсов

13

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

public interface IContextProvider
{
   IDataContext { get; set; }
   IAreaContext { get; set; }
}

Этот интерфейс часто используется во всей программе, и поэтому у меня есть легкий доступ к нужным объектам. Однако на довольно низком уровне части моей программы мне нужен доступ к другому классу, который будет использовать IAreaContext и выполнять над ним некоторые операции. Поэтому я создал еще один фабричный интерфейс для этого создания, который называется:

public interface IEventContextFactory 
{
   IEventContext CreateEventContext(int eventId);
}

У меня есть класс, который реализует IContextProvider и вводится с помощью NinJect. У меня проблема в том, что область, где мне нужно использовать этот IEventContextFactory, имеет доступ только к IContextProvider и сама использует другой класс, который будет нуждаться в этом новом интерфейсе. Я не хочу создавать экземпляр этой реализации IEventContextFactory на низком уровне и предпочел бы работать с интерфейсом IEventContextFactory повсюду. Однако я также не хочу вводить другой параметр через конструкторы, просто чтобы передать его классу, который нуждается в этом, т.е.

// example of problem
public class MyClass
{
    public MyClass(IContextProvider context, IEventContextFactory event)
    {
       _context = context;
       _event = event;
    }

    public void DoSomething() 
    {
       // the only place _event is used in the class is to pass it through
       var myClass = new MyChildClass(_event);
       myClass.PerformCalculation();      
    }
}

Поэтому мой главный вопрос: приемлемо ли это, или это обычное дело или хорошая практика сделать что-то вроде этого (интерфейс расширяет другой интерфейс):

public interface IContextProvider : IEventContextFactory

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

dreza
источник
2
Я бы перефразировал заголовок вашего вопроса как «Должны ли интерфейсы наследовать интерфейсы», поскольку ни один интерфейс на самом деле ничего не реализует.
Джесси С. Слайсер
2
Обычный язык состоит в том, что интерфейс «расширяет» своего родителя и (при этом) «наследует» методы родительского интерфейса, поэтому я бы рекомендовал использовать здесь «расширение».
Теодор Мердок
Интерфейсы могут расширять возможности родителей, так почему это будет плохой практикой? Если один интерфейс на самом деле является супер-набором другого интерфейса, то, конечно, он должен его расширять.
Робин Уинслоу

Ответы:

10

Хотя интерфейсы описывают только поведение без какой-либо реализации, они все же могут участвовать в иерархиях наследования. В качестве примера представьте, что у вас есть, скажем, общая идея о Collection. Этот интерфейс предоставит несколько общих для всех коллекций вещей, например, метод для перебора членов. Затем вы можете подумать о некоторых более конкретных коллекциях объектов, таких как a Listили a Set. Каждый из них наследует все требования поведения a Collection, но добавляет дополнительные требования, специфичные для этого конкретного типа коллекции.

В любое время имеет смысл создать иерархию наследования для интерфейсов, тогда вам следует это сделать. Если два интерфейса всегда будут найдены вместе, то их, вероятно, следует объединить.

Zoe
источник
6

Да, но только если это имеет смысл. Задайте себе следующие вопросы, и если один или несколько из них применимы, то приемлемо наследовать интерфейс от другого.

  1. Интерфейс A логически расширяет интерфейс B, например, добавляет IList функциональность в ICollection.
  2. Нужно ли интерфейсу A определять поведение, уже заданное другим интерфейсом, например, реализовывать IDisposable, если у него есть ресурсы, которые необходимо привести в порядок, или IEnumerable, если его можно повторять.
  3. Требует ли каждая реализация интерфейса A поведения, определенного интерфейсом B?

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

public interface IContextProvider
{
   IDataContext DataContext { get; set; }
   IAreaContext AreaContext { get; set; }
   IEventContextFactory EventContextFactory { get; set; }
}
Тревор Пилли
источник
Как сделать это свойство лучше, чем расширять интерфейс, или это просто еще один способ, чтобы сказать, так сказать?
dreza
1
Разница в том, что если вы сделаете IContextProviderextension IEventContextFactory, то ContextProviderреализация должна будет реализовать и этот метод. Если вы добавляете его как свойство, вы говорите, что он ContextProviderимеет, IEventContextFactoryно на самом деле не знает деталей реализации.
Тревор Пилли
4

Да, это нормально, чтобы расширить один интерфейс из другого.

Вопрос о том, правильно ли это делать в конкретном случае, зависит от того, существуют ли истинные отношения «есть» между ними.

Что требуется для правильных отношений "is-a"?

Во-первых, между двумя интерфейсами должна быть реальная разница, т. Е. Должны быть экземпляры, которые реализуют родительский интерфейс, но не дочерний интерфейс. Если нет и никогда не будет экземпляров, которые не реализуют оба интерфейса, то вместо этого эти два интерфейса должны быть объединены.

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

Если оба из них верны, то наследование является правильным отношением для двух интерфейсов.

Теодор Мердок
источник
Я думаю, что наличие классов, которые используют только базовый интерфейс, а не производный, не является необходимым. Например, интерфейс IReadOnlyListможет быть полезен, даже если все классы моего списка реализуют IList(который получен из IReadOnlyList).
svick
1

Если один интерфейс всегда хочет запрашивать / предоставлять другой, тогда продолжайте. Я видел это в нескольких случаях, IDisposibleкоторые имели смысл. В общем, вы должны убедиться, что хотя бы один из интерфейсов ведет себя скорее как черта, а не ответственность.

Для вашего примера это не понятно. Если они оба всегда вместе, просто создайте один интерфейс. Если один всегда нуждается в другом, меня беспокоит наследование типа «X и 1», которое очень часто приводит к множественным обязанностям и / или другим проблемам с утечкой абстракции.

Telastyn
источник
Спасибо за это. Что вы подразумеваете под поведением больше как черта, чем ответственность?
дреза
@dreza Я имею в виду ответственность, как в SRP в SOLID, и черта, как что-то вроде черт Scala. В основном концепция того, как интерфейс моделирует вашу проблему. Представляет ли это основную часть проблемы или взгляд на общую часть других несопоставимых типов?
Теластин