Якщо інтерфейси поширюватимуться (і при цьому успадковуватимуть інші методи)


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

чи варто розглянути кращі альтернативи для досягнення того, що мені потрібно. Якщо я не надав достатньо інформації, щоб дати пропозиції, дайте мені знати, і я можу надати більше.


2
Я б переформулював назву вашого питання як "Чи повинні інтерфейси успадковувати інтерфейси", оскільки жоден інтерфейс насправді нічого не реалізує.
Jesse C. Slicer

2
Звичайна мова полягає в тому, що інтерфейс "розширює" його батьківський (і, роблячи це) "успадковує" методи батьківського інтерфейсу, тому я рекомендую тут використовувати "Extend".
Теодор Мердок

Інтерфейси можуть поширювати батьків, то чому б це було поганою практикою? Якщо один інтерфейс є законним набором іншого інтерфейсу, ніж, звичайно, його слід розширити.
Робін Уінслоу

Відповіді:


10

Хоча інтерфейси описують лише поведінку без будь-якої реалізації, вони все ще можуть брати участь у ієрархіях спадкування. Як приклад, уявіть, що у вас є, скажімо, загальна ідея а Collection. Цей інтерфейс забезпечить декілька речей, які є загальними для всіх колекцій, наприклад, метод ітерації над членами. Тоді ви можете подумати про деякі більш конкретні колекції об'єктів, таких як a Listчи a Set. Кожен із них успадковує всі вимоги до поведінки Collection, але додає додаткові вимоги, характерні саме для цього виду колекції.

Щоразу, коли має сенс створити ієрархію спадкування для інтерфейсів, тоді слід. Якщо два інтерфейси завжди знайдуться разом, то, ймовірно, їх слід об'єднати.


6

Так, але тільки якщо це має сенс. Задайте собі наступні запитання, і якщо одне або більше застосувати, то допустимо успадкувати інтерфейс від іншого.

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

У вашому прикладі, я не думаю, що це відповідає так на жоден із них. Можливо, вам буде краще додати його як іншу властивість:

public interface IContextProvider
{
   IDataContext DataContext { get; set; }
   IAreaContext AreaContext { get; set; }
   IEventContextFactory EventContextFactory { get; set; }
}

Як зробити так, щоб ця властивість була кращою, ніж розширення інтерфейсу, або це просто ще один спосіб зіпсувати кішку, так би мовити?
dreza

1
Різниця полягає в тому, що якщо ви зробите IContextProviderрозширення IEventContextFactory, то для ContextProviderвпровадження знадобиться і цей метод. Якщо ви додасте його як властивість, ви говорите про те, що " ContextProviderмає", IEventContextFactoryале фактично не знає деталей щодо реалізації.
Тревор Піллі

4

Так, чудово розширити один інтерфейс від іншого.

Питання про те, чи правильно це робити в конкретному випадку, залежить від того, чи є між ними справжні стосунки "є-а".

Що потрібно для дійсних відносин "є-а"?

По-перше, між двома інтерфейсами повинна бути реальна різниця, тобто повинні бути випадки, які реалізують батьківський інтерфейс, але не дочірній інтерфейс. Якщо немає і ніколи не буде примірників, які не реалізують обидва інтерфейси, то два інтерфейси слід замість цього об'єднати.

По-друге, повинно бути так, що кожен екземпляр дочірнього інтерфейсу обов'язково повинен бути екземпляром батьківського: немає (розумної) логіки, за якою ви створювали б примірник дочірнього інтерфейсу, який би не мав сенсу як екземпляр батьківський інтерфейс.

Якщо обидва ці вірні, то для двох інтерфейсів успадкування - це правильне співвідношення.


Я думаю, що класи, які використовують тільки базовий інтерфейс, а не похідний, не потрібні. Наприклад, інтерфейс IReadOnlyListможе бути корисним, навіть якщо реалізуються всі мої класи списків IList(що походить від IReadOnlyList).
svick

1

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

Для вашого прикладу це не ясно. Якщо вони обоє завжди разом, просто зробіть один інтерфейс. Якщо одній завжди потрібна інша, мене б хвилювало питання спадкування типу "X і 1", яке дуже часто призводить до численних обов'язків та / або інших проблем, що протікають з абстракцією.


Дякую за це. Що ви маєте на увазі під тим, що поводитесь більше як риса, ніж відповідальність?
dreza

@dreza Я маю на увазі відповідальність, як у SRP, у SOLID, та є такою, як риси Scala. Переважно концепція того, як інтерфейс моделює вашу проблему. Чи представляє вона первинну частину проблеми чи погляд на загальну частину інакше розрізнених типів?
Теластин
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.