Jeśli interfejsy rozszerzają się (a tym samym dziedziczą metody) innych interfejsów

13

Chociaż jest to pytanie ogólne, jest ono również specyficzne dla problemu, którego obecnie doświadczam. Obecnie w interfejsie mam określony interfejs o nazwie

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

Ten interfejs jest często używany w całym programie, dlatego mam łatwy dostęp do potrzebnych mi obiektów. Jednak na dość niskim poziomie części mojego programu potrzebuję dostępu do innej klasy, która użyje IAreaContext i wykona niektóre operacje poza tym. Więc stworzyłem inny interfejs fabryczny, aby wykonać to stworzenie o nazwie:

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

Mam klasę, która implementuje IContextProvider i jest wstrzykiwana przy użyciu NinJect. Problem, który mam, polega na tym, że obszar, w którym muszę użyć tego IEventContextFactory, ma dostęp tylko do IContextProvider i sam używa innej klasy, która będzie potrzebowała tego nowego interfejsu. Nie chcę tworzyć instancji tej implementacji IEventContextFactory na niskim poziomie i wolę raczej pracować z interfejsem IEventContextFactory . Jednak nie chcę też wstrzykiwać innego parametru przez konstruktory, aby przekazać go do klasy, która tego potrzebuje, tj

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

Więc moje główne pytanie brzmi: czy byłoby to do przyjęcia, czy jest to nawet powszechna lub dobra praktyka, aby zrobić coś takiego (interfejs rozszerza inny interfejs):

public interface IContextProvider : IEventContextFactory

czy powinienem rozważyć lepszą alternatywę dla osiągnięcia tego, czego potrzebuję. Jeśli nie podałem wystarczających informacji, aby podać sugestie, daj mi znać, a mogę podać więcej.

dreza
źródło
2
Chciałbym przeredagować tytuł pytania na „Interfejsy powinny dziedziczyć interfejsy”, ponieważ żaden interfejs tak naprawdę niczego nie implementuje.
Jesse C. Slicer
2
Zwykle język polega na tym, że interfejs „rozszerza” swój element nadrzędny i (w ten sposób) „dziedziczy” metody interfejsu nadrzędnego, dlatego zaleciłbym użycie tutaj „rozszerzenia”.
Theodore Murdock
Interfejsy mogą rozszerzyć rodziców, więc dlaczego miałoby to być złą praktyką? Jeśli jeden interfejs jest uzasadnionym super-zestawem innego interfejsu, to oczywiście powinien go rozszerzyć.
Robin Winslow,

Odpowiedzi:

10

Chociaż interfejsy opisują tylko zachowanie bez żadnej implementacji, nadal mogą uczestniczyć w hierarchiach dziedziczenia. Jako przykład wyobraź sobie, że masz, powiedzmy, wspólną ideę Collection. Ten interfejs zapewnia kilka rzeczy, które są wspólne dla wszystkich kolekcji, na przykład metodę iteracji elementów. Następnie możesz pomyśleć o bardziej szczegółowych kolekcjach obiektów, takich jak a Listlub a Set. Każdy z nich dziedziczy wszystkie wymagania dotyczące zachowania Collection, ale dodaje dodatkowe wymagania specyficzne dla tego konkretnego typu kolekcji.

Ilekroć ma sens tworzenie hierarchii dziedziczenia dla interfejsów, powinieneś to zrobić. Jeśli dwa interfejsy zawsze będą znajdować się razem, prawdopodobnie powinny zostać połączone.

Zoe
źródło
6

Tak, ale tylko jeśli ma to sens. Zadaj sobie następujące pytania, a jeśli jedno lub więcej ma zastosowanie, wówczas można odziedziczyć interfejs od innego.

  1. Czy interfejs A logicznie rozszerza interfejs B, na przykład IList dodaje funkcjonalność do ICollection.
  2. Czy interfejs Konieczne jest zdefiniowanie zachowania określonego już przez inny interfejs, na przykład implementacja IDisposable, jeśli ma zasoby, które należy uporządkować lub IEnumerable, jeśli można je iterować.
  3. Czy każda implementacja interfejsu A wymaga zachowania określonego przez interfejs B?

W twoim przykładzie nie sądzę, że odpowiedź na którekolwiek z nich brzmi „tak”. Lepiej dodaj ją jako inną właściwość:

public interface IContextProvider
{
   IDataContext DataContext { get; set; }
   IAreaContext AreaContext { get; set; }
   IEventContextFactory EventContextFactory { get; set; }
}
Trevor Pilley
źródło
W jaki sposób uczynienie go właściwością byłoby lepsze niż rozszerzenie interfejsu, czy też jest to po prostu inny sposób na skórowanie kota?
dreza
1
Różnica polega na tym, że jeśli IContextProviderrozszerzysz IEventContextFactory, ContextProviderimplementacja będzie musiała również zaimplementować tę metodę. Jeśli dodasz go jako właściwość, mówisz, że ContextProviderma, IEventContextFactoryale tak naprawdę nie zna szczegółów implementacji.
Trevor Pilley,
4

Tak, dobrze jest rozszerzyć jeden interfejs z drugiego.

Pytanie, czy jest to właściwe w konkretnym przypadku, zależy od tego, czy istnieje prawdziwy związek między nimi.

Co jest wymagane do prawidłowego związku „jest-a”?

Po pierwsze, musi istnieć prawdziwa różnica między dwoma interfejsami, tzn. Muszą istnieć instancje, które implementują interfejs nadrzędny, ale nie interfejs podrzędny. Jeśli nie ma i nigdy nie będzie instancji, które nie implementują obu interfejsów, wówczas dwa interfejsy powinny zostać połączone.

Po drugie, musi się zdarzyć, że każda instancja interfejsu potomnego musi koniecznie być instancją nadrzędną: nie ma (rozsądnej) logiki, w której stworzyłbyś instancję interfejsu potomnego, która nie miałaby sensu jako instancja interfejs nadrzędny.

Jeśli oba są prawdziwe, to dziedziczenie jest właściwą relacją dla dwóch interfejsów.

Theodore Murdock
źródło
Myślę, że posiadanie klas, które używają tylko interfejsu podstawowego, a nie pochodnego, nie jest konieczne. Na przykład interfejs IReadOnlyListmoże być przydatny, nawet jeśli implementują wszystkie moje klasy list IList(z których pochodzi IReadOnlyList).
svick
1

Jeśli jeden interfejs zawsze chce wymagać / udostępniać drugi, to śmiało. Widziałem to w kilku przypadkach zIDisposible które miały sens. Ogólnie rzecz biorąc, powinieneś jednak upewnić się, że przynajmniej jeden z interfejsów zachowuje się bardziej jak cecha niż odpowiedzialność.

W twoim przykładzie nie jest jasne. Jeśli oba są zawsze razem, po prostu stwórz jeden interfejs. Jeśli jedno zawsze potrzebuje drugiego, martwiłbym się dziedziczeniem „X i 1”, które bardzo często prowadzi do wielu obowiązków i / lub innych nieszczelnych problemów związanych z abstrakcją.

Telastyn
źródło
Dziękuję za to. Co masz na myśli mówiąc, że zachowujesz się bardziej jak cecha niż odpowiedzialność?
dreza
@dreza Mam na myśli odpowiedzialność jak w SRP w SOLID, i cechuję się jak cechy Scali. Głównie koncepcja tego, jak interfejs modeluje twój problem. Czy stanowi podstawową część problemu, czy też pogląd na wspólny kawałek różnych rodzajów, które byłyby odmienne?
Telastyn