Mam klasę z pewnymi domyślnymi / współdzielonymi funkcjami. Używam abstract class
do tego:
public interface ITypeNameMapper
{
string Map(TypeDefinition typeDefinition);
}
public abstract class TypeNameMapper : ITypeNameMapper
{
public virtual string Map(TypeDefinition typeDefinition)
{
if (typeDefinition is ClassDefinition classDefinition)
{
return Map(classDefinition);
}
...
throw new ArgumentOutOfRangeException(nameof(typeDefinition));
}
protected abstract string Map(ClassDefinition classDefinition);
}
Jak widać, mam również interfejs ITypeNameMapper
. Czy zdefiniowanie tego interfejsu ma sens, jeśli mam już klasę abstrakcyjną TypeNameMapper
lub abstract class
wystarczy?
TypeDefinition
w tym minimalnym przykładzie jest również abstrakcyjny.
c#
interfaces
abstract-class
Konrad
źródło
źródło
Odpowiedzi:
Tak, ponieważ C # nie zezwala na wielokrotne dziedziczenie oprócz interfejsów.
Więc jeśli mam klasę, która jest zarówno TypeNameMapper, jak i SomethingelseMapper, mogę:
źródło
ICanFly
,ICanRun
,ICanSwim
. Musisz stworzyć 8 klas stworzeń, po jednej dla każdej kombinacji cech (RRRR, YYN, YNY, ..., NNY, NNN). SRP nakazuje, abyś zaimplementował każde zachowanie (latanie, bieganie, pływanie), a następnie zaimplementował je ponownie na 8 stworzeniach. Kompozycja byłaby tutaj jeszcze lepsza, ale ignorując kompozycję na sekundę, powinieneś już widzieć potrzebę dziedziczenia / implementacji wielu osobnych zachowań wielokrotnego użytku z powodu SRP.interface
.. nadmiarowe implementacje, które powinny znajdować się w bazie - które ewoluują niezależnie !, warunkowe zmiany logiczne napędzające to śmieci z powodu braku metod szablonów, zepsutego hermetyzacji, nieistniejącej abstrakcji. Moje ulubione: klasy 1-metodowe, z których każda implementuje własną metodę 1interface
, każda w / tylko jedna instancja. ... itd. itd. itd.Interfejsy i klasy abstrakcyjne służą różnym celom:
W twoim przykładzie
interface ITypeNameMapper
określa potrzeby klientów iabstract class TypeNameMapper
nie dodaje żadnej wartości.źródło
public
należy do klienta.TypeNameMapper
dodaje wiele wartości, ponieważ oszczędza implementatorowi napisanie wspólnej logiki.interface
czy nie, jest istotne tylko w tym, że C # zabrania pełnego wielokrotnego dziedziczenia.Cała koncepcja interfejsów została stworzona w celu wspierania rodziny klas posiadających wspólne API.
To wyraźnie mówi, że każde użycie interfejsu oznacza (lub oczekuje się, że) będzie więcej niż jedną implementację jego specyfikacji.
Frameworki DI zamulają wody tutaj, ponieważ wiele z nich wymaga interfejsu, chociaż będzie zawsze tylko jedna implementacja - dla mnie jest to nieracjonalne obciążenie ogólne, ponieważ w większości przypadków jest to po prostu bardziej złożony i wolniejszy sposób wywoływania nowego, ale to jest co to jest.
Odpowiedź leży w samym pytaniu. Jeśli masz klasę abstrakcyjną, przygotowujesz się do utworzenia więcej niż jednej klasy pochodnej ze wspólnym interfejsem API. Dlatego użycie interfejsu jest wyraźnie wskazane.
źródło
find ... -exec perl -pi -e ...
naprawienie trywialnych błędów kompilacji. Chodzi mi o to: korzyść z posiadania (obecnie) bezużytecznej rzeczy jest znacznie większa niż możliwe przyszłe oszczędności.interface
jako kod (mówisz o C # -interface
sach, a nie abstrakcyjnym interfejsie, jak każdy zestaw klas / funkcji / itp.), I zakończyłeś je "... ale wciąż zakazujesz pełnej wielokrotności dziedzictwo." Nie ma potrzeby,interface
jeśli masz MI.Jeśli chcemy wyraźnie odpowiedzieć na pytanie, autor mówi: „Czy sensowne jest zdefiniowanie tego interfejsu, jeśli mam już klasę abstrakcyjną TypeNameMapper lub klasa abstrakcyjna wystarczy?”
Odpowiedź brzmi: tak i nie - tak, powinieneś utworzyć interfejs, nawet jeśli masz już abstrakcyjną klasę podstawową (ponieważ nie powinieneś odnosić się do abstrakcyjnej klasy bazowej w żadnym kodzie klienta) i nie, ponieważ nie powinieneś był tworzyć abstrakcyjna klasa bazowa przy braku interfejsu.
Oczywiste jest, że nie włożyłeś wystarczającej uwagi w API, które próbujesz zbudować. Najpierw wymyśl interfejs API, a następnie w razie potrzeby zapewnij częściową implementację. Fakt, że twoja abstrakcyjna klasa bazowa sprawdza kod w kodzie, wystarcza, aby powiedzieć, że nie jest to właściwa abstrakcja.
Podobnie jak w większości rzeczy w OOD, kiedy jesteś w ruchu i dobrze wykonujesz swój model obiektowy, twój kod poinformuje cię, co będzie dalej. Zacznij od interfejsu, zaimplementuj ten interfejs w potrzebnych klasach. Jeśli zauważysz, że piszesz podobny kod, wyodrębnij go do abstrakcyjnej klasy bazowej - ważny jest interfejs, abstrakcyjna klasa podstawowa jest tylko pomocnikiem i może istnieć więcej niż jedna.
źródło
Odpowiedź na to pytanie jest trudna bez znajomości reszty aplikacji.
Zakładając, że używasz jakiegoś modelu DI i zaprojektowałeś swój kod tak, aby był możliwie jak najbardziej rozszerzalny (abyś mógł
TypeNameMapper
łatwo dodawać nowe ), powiedziałbym tak.Powody dla:
interface
, czego nie musisz się martwić w żadnych implementacjach potomnychinterface
s. Chociaż wiele z nich pracuje z klasami abstrakcyjnymi, nie zawsze jest to pewne i jestem wielkim zwolennikiem podążania tą samą ścieżką, co pozostałe 99% użytkowników biblioteki, tam gdzie to możliwe.Powody przeciwko:
class
/interface
zmienia się, pojawia się trochę dodatkowego obciążeniaBiorąc wszystko pod uwagę, powiedziałbym, że powinieneś stworzyć
interface
. Przyczyny są dość znikome, ale chociaż możliwe jest użycie abstrakcji w kpinach i IoC, jest to o wiele łatwiejsze dzięki interfejsom. Ostatecznie jednak nie skrytykowałbym kodu kolegi, gdyby poszedł w drugą stronę.źródło