To może być ogólne pytanie dotyczące OOP. Chciałem dokonać ogólnego porównania interfejsu z klasą abstrakcyjną na podstawie ich użycia.
Kiedy chciałoby się użyć interfejsu, a kiedy klasy abstrakcyjnej ?
To może być ogólne pytanie dotyczące OOP. Chciałem dokonać ogólnego porównania interfejsu z klasą abstrakcyjną na podstawie ich użycia.
Kiedy chciałoby się użyć interfejsu, a kiedy klasy abstrakcyjnej ?
Odpowiedzi:
Napisałem o tym artykuł:
Klasy abstrakcyjne i interfejsy
Zreasumowanie:
Kiedy mówimy o klasach abstrakcyjnych, definiujemy cechy typu obiektu; określając, czym jest obiekt .
Kiedy mówimy o interfejsie i definiujemy możliwości, które obiecujemy zapewnić, mówimy o zawarciu umowy o tym, co może zrobić obiekt.
Interfaces do not express something like "a Doberman is a type of dog and every dog can walk" but more like "this thing can walk"
. Dziękuję
Use abstract classes and inheritance if you can make the statement “A is a B”. Use interfaces if you can make the statement “A is capable of [doing] as”
Klasa abstrakcyjna może mieć stan współdzielony lub funkcjonalność. Interfejs to tylko obietnica zapewniająca stan lub funkcjonalność. Dobra klasa abstrakcyjna zmniejszy ilość kodu, który trzeba przepisać, ponieważ można udostępnić jego funkcjonalność lub stan. Interfejs nie ma zdefiniowanych informacji do udostępnienia
Osobiście prawie nigdy nie muszę pisać klas abstrakcyjnych.
Najczęściej widzę, że klasy abstrakcyjne są używane (źle), ponieważ autor klasy abstrakcyjnej używa wzorca „Metoda szablonu”.
Problem z „metodą szablonów” polega na tym, że prawie zawsze jest ona w pewnym stopniu ponownie uruchamiana - klasa „pochodna” wie nie tylko o „abstrakcyjnej” metodzie swojej klasy bazowej, którą implementuje, ale także o metodach publicznych klasy podstawowej , mimo że w większości przypadków nie trzeba do nich dzwonić.
(Zbyt uproszczony) przykład:
abstract class QuickSorter
{
public void Sort(object[] items)
{
// implementation code that somewhere along the way calls:
bool less = compare(x,y);
// ... more implementation code
}
abstract bool compare(object lhs, object rhs);
}
Tak więc tutaj autor tej klasy napisał ogólny algorytm i chce, aby ludzie go używali, „specjalizując się”, zapewniając własne „zaczepy” - w tym przypadku metodę „porównania”.
Tak więc zamierzone użycie jest mniej więcej takie:
class NameSorter : QuickSorter
{
public bool compare(object lhs, object rhs)
{
// etc.
}
}
Problem polega na tym, że nadmiernie połączyłeś dwie koncepcje:
W powyższym kodzie teoretycznie autor metody „porównaj” może ponownie wywołać metodę superklasy „Sortuj” ... nawet jeśli w praktyce nigdy nie będzie tego chciał ani nie musiał.
Cena, którą płacisz za to niepotrzebne sprzężenie, polega na tym, że trudno jest zmienić nadklasę, aw większości języków OO nie można jej zmienić w czasie wykonywania.
Alternatywną metodą jest użycie zamiast tego wzorca projektowego „Strategia”:
interface IComparator
{
bool compare(object lhs, object rhs);
}
class QuickSorter
{
private readonly IComparator comparator;
public QuickSorter(IComparator comparator)
{
this.comparator = comparator;
}
public void Sort(object[] items)
{
// usual code but call comparator.Compare();
}
}
class NameComparator : IComparator
{
bool compare(object lhs, object rhs)
{
// same code as before;
}
}
Więc zauważ teraz: wszystko, co mamy, to interfejsy i konkretne implementacje tych interfejsów. W praktyce tak naprawdę nie potrzebujesz niczego więcej, aby wykonać projekt OO na wysokim poziomie.
Aby „ukryć” fakt, że zaimplementowaliśmy „sortowanie nazw” za pomocą klasy „QuickSort” i „NameComparator”, możemy gdzieś napisać metodę fabryczną:
ISorter CreateNameSorter()
{
return new QuickSorter(new NameComparator());
}
Za każdym razem, gdy masz klasę abstrakcyjną, możesz to zrobić ... nawet jeśli istnieje naturalna relacja ponownego wejścia między klasą podstawową i pochodną, zwykle opłaca się je wyrazić.
Ostatnia myśl: wszystko, co zrobiliśmy powyżej, to „skomponowanie” funkcji „NameSorting” za pomocą funkcji „QuickSort” i funkcji „NameComparison” ... w funkcjonalnym języku programowania ten styl programowania staje się jeszcze bardziej naturalny, z mniejszym kodem.
Jeśli patrzysz na java jako język OOP,
„ Interfejs nie zapewnia implementacji metody ” nie jest już ważny w przypadku uruchomienia Java 8. Teraz Java zapewnia implementację interfejsu dla metod domyślnych.
Mówiąc najprościej, chciałbym użyć
interfejs: Aby wdrożyć umowę przez wiele niepowiązanych obiektów. Zapewnia funkcję „ HAS A ”.
Klasa abstrakcyjna: Aby zaimplementować to samo lub inne zachowanie wśród wielu powiązanych obiektów. Ustanawiarelację„ JEST A ”.
Oracle strona dostarcza kluczowych różnic pomiędzy interface
i abstract
klasę.
Rozważ użycie klas abstrakcyjnych, jeśli:
Rozważ użycie interfejsów, jeśli:
Serializable
interfejs.Przykład:
Klasa abstrakcyjna ( relacja IS A )
Reader to klasa abstrakcyjna.
BufferedReader toReader
FileReader toReader
FileReader
i BufferedReader
są wykorzystywane do wspólnego celu: Odczytywanie danych i są one powiązane poprzez Reader
klasę.
Interfejs ( funkcja HAS A )
Serializable to interfejs.
Załóżmy, że masz dwie klasy aplikacji, które implementują Serializable
interfejs
Employee implements Serializable
Game implements Serializable
Tutaj nie można ustanowić żadnej relacji poprzez Serializable
interfejs między Employee
i Game
, które są przeznaczone do różnych celów. Oba są w stanie szeregować stan, a porównanie kończy się na tym.
Spójrz na te posty:
Jak powinienem wyjaśnić różnicę między interfejsem a klasą abstrakcyjną?
OK, po tym, jak sam to „wymamrotałem” - tutaj jest to w kategoriach laika (możesz mnie poprawić, jeśli się mylę) - Wiem, że ten temat jest przestarzały, ale ktoś inny może się na niego natknąć pewnego dnia ...
Klasy abstrakcyjne umożliwiają tworzenie schematu i pozwalają dodatkowo KONSTRUKOWAĆ (implementować) właściwości i metody, które mają WSZYSTKIE potomkowie.
Z drugiej strony interfejs pozwala tylko zadeklarować, że chcesz, aby właściwości i / lub metody o danej nazwie istniały we wszystkich klasach, które je implementują - ale nie określa, w jaki sposób powinieneś je wdrożyć. Ponadto klasa może implementować WIELE interfejsów, ale może rozszerzyć tylko JEDNĄ klasę abstrakcyjną. Interfejs to bardziej narzędzie architektoniczne wysokiego poziomu (które staje się bardziej zrozumiałe, jeśli zaczniesz rozumieć wzorce projektowe) - Streszczenie ma stopę w obu obozach i może również wykonywać niektóre brudne prace.
Po co używać jednego na drugim? Pierwszy pozwala na bardziej konkretną definicję potomków - drugi pozwala na większy polimorfizm . Ten ostatni punkt jest ważny dla końcowego użytkownika / kodera, który może wykorzystać te informacje do implementacji AP I (nterface) w różnych kombinacjach / kształtach w zależności od potrzeb.
Myślę, że był to dla mnie moment „żarówka” - myślę o interfejsach mniej z perspektywy autora, a więcej z koderem pochodzącym z późniejszego łańcucha, który dodaje implementację do projektu lub rozszerza API.
Moje dwa centy:
Interfejs zasadniczo definiuje umowę, do której musi się stosować każda klasa implementująca (implementująca elementy interfejsu). Nie zawiera żadnego kodu.
Z drugiej strony klasa abstrakcyjna może zawierać kod i mogą istnieć pewne metody oznaczone jako abstrakcyjne, które klasa dziedzicząca musi zaimplementować.
Rzadkie sytuacje, w których korzystałem z klas abstrakcyjnych, polegają na tym, że mam domyślną funkcjonalność, której dziedziczenie może nie być interesujące w przypadku przesłonięcia, powiedzmy abstrakcyjnej klasy bazowej, z której dziedziczą niektóre klasy specjalistyczne.
Przykład (bardzo prymitywny jeden!): Rozważmy klasy bazowej o nazwie klienta, który ma abstrakcyjnych metod, takich jak CalculatePayment()
, CalculateRewardPoints()
i pewne metody abstrakcyjne, takie jak GetName()
, SavePaymentDetails()
.
Wyspecjalizowane klasy, takie jak RegularCustomer
i GoldCustomer
odziedziczą po Customer
klasie podstawowej i implementują własną logikę CalculatePayment()
i CalculateRewardPoints()
metody, ale ponownie wykorzystują metody GetName()
i SavePaymentDetails()
.
Możesz dodać więcej funkcji do klasy abstrakcyjnej (czyli metod nieabstrakcyjnych) bez wpływu na klasy potomne, które korzystały ze starszej wersji. Podczas gdy dodawanie metod do interfejsu wpłynęłoby na wszystkie klasy implementujące go, ponieważ musiałyby one teraz implementować nowo dodane elementy interfejsu.
Klasa abstrakcyjna ze wszystkimi elementami abstrakcyjnymi byłaby podobna do interfejsu.
Kiedy robić to, co jest bardzo proste, jeśli masz jasną koncepcję.
Klasy abstrakcyjne można uzyskać, natomiast interfejsy można zaimplementować. Istnieje pewna różnica między nimi. Kiedy wyprowadzasz klasę abstrakcyjną, związek między klasą pochodną a klasą podstawową jest relacją „to”. np. pies to zwierzę, owca to zwierzę, co oznacza, że klasa pochodna dziedziczy niektóre właściwości z klasy podstawowej.
Natomiast w przypadku implementacji interfejsów relacja jest „może być”. np. Pies może być psem szpiegowskim. Pies może być psem cyrkowym. Pies może być psem wyścigowym. Co oznacza, że wdrażasz określone metody uzyskania czegoś.
Mam nadzieję, że wszystko jasne.
1. Jeśli tworzysz coś, co zapewnia wspólną funkcjonalność niepowiązanym klasom, użyj interfejsu.
2. Jeśli tworzysz coś dla obiektów blisko powiązanych w hierarchii, użyj klasy abstrakcyjnej.
Napisałem artykuł o tym, kiedy użyć klasy abstrakcyjnej, a kiedy użyć interfejsu. Jest o wiele więcej różnic między nimi niż „jeden IS-A ... i jeden CAN-DO…”. Dla mnie są to odpowiedzi w puszkach. Podaję kilka powodów, dla których warto użyć jednego z nich. Mam nadzieję, że to pomoże.
Myślę, że najbardziej zwięzły sposób to:
Wspólne właściwości => klasa abstrakcyjna.
Wspólna funkcjonalność => interfejs.
I mówiąc mniej zwięźle ...
Przykład klasy abstrakcyjnej:
public abstract class BaseAnimal
{
public int NumberOfLegs { get; set; }
protected BaseAnimal(int numberOfLegs)
{
NumberOfLegs = numberOfLegs;
}
}
public class Dog : BaseAnimal
{
public Dog() : base(4) { }
}
public class Human : BaseAnimal
{
public Human() : base(2) { }
}
Ponieważ zwierzęta mają wspólną właściwość - w tym przypadku liczbę nóg - warto stworzyć klasę abstrakcyjną zawierającą tę wspólną właściwość. Pozwala nam to również napisać wspólny kod, który działa na tej właściwości. Na przykład:
public static int CountAllLegs(List<BaseAnimal> animals)
{
int legCount = 0;
foreach (BaseAnimal animal in animals)
{
legCount += animal.NumberOfLegs;
}
return legCount;
}
Przykład interfejsu:
public interface IMakeSound
{
void MakeSound();
}
public class Car : IMakeSound
{
public void MakeSound() => Console.WriteLine("Vroom!");
}
public class Vuvuzela : IMakeSound
{
public void MakeSound() => Console.WriteLine("VZZZZZZZZZZZZZ!");
}
Zauważ, że Vuvuzelas i Samochody to zupełnie inne rzeczy, ale mają wspólną funkcjonalność: wydawanie dźwięków. Dlatego interfejs ma tutaj sens. Ponadto pozwoli programistom grupować rzeczy, które wydają dźwięki razem, we wspólnym interfejsie - IMakeSound
w tym przypadku. Za pomocą tego projektu możesz napisać następujący kod:
List<IMakeSound> soundMakers = new List<ImakeSound>();
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Car());
soundMakers.Add(new Vuvuzela());
soundMakers.Add(new Vuvuzela());
foreach (IMakeSound soundMaker in soundMakers)
{
soundMaker.MakeSound();
}
Czy możesz powiedzieć, co by to dało?
Na koniec możesz połączyć te dwa elementy.
Połączony przykład:
public interface IMakeSound
{
void MakeSound();
}
public abstract class BaseAnimal : IMakeSound
{
public int NumberOfLegs { get; set; }
protected BaseAnimal(int numberOfLegs)
{
NumberOfLegs = numberOfLegs;
}
public abstract void MakeSound();
}
public class Cat : BaseAnimal
{
public Cat() : base(4) { }
public override void MakeSound() => Console.WriteLine("Meow!");
}
public class Human : BaseAnimal
{
public Human() : base(2) { }
public override void MakeSound() => Console.WriteLine("Hello, world!");
}
Tutaj wymagamy, aby wszyscy BaseAnimal
wydawali dźwięki, ale nie znamy jeszcze jego implementacji. W takim przypadku możemy wyodrębnić implementację interfejsu i przekazać jej implementację do jego podklas.
Ostatni punkt, pamiętasz, jak w przykładzie klasy abstrakcyjnej mogliśmy operować na wspólnych właściwościach różnych obiektów, aw przykładzie interfejsu mogliśmy wywoływać wspólną funkcjonalność różnych obiektów? W tym ostatnim przykładzie moglibyśmy zrobić jedno i drugie.
Kiedy preferować klasę abstrakcyjną zamiast interfejsu?
Kiedy preferować interfejs zamiast klasy abstrakcyjnej?
Klasy mogą dziedziczyć tylko jedną klasę podstawową, więc jeśli chcesz użyć klas abstrakcyjnych w celu zapewnienia polimorfizmu grupie klas, wszystkie muszą dziedziczyć po tej klasie. Klasy abstrakcyjne mogą również zapewniać członków, które zostały już zaimplementowane. Dlatego możesz zapewnić pewną ilość identycznej funkcjonalności z klasą abstrakcyjną, ale nie możesz z interfejsem.
Oto kilka zaleceń, które pomogą Ci zdecydować, czy użyć interfejsu, czy klasy abstrakcyjnej, aby zapewnić polimorfizm komponentów.
Skopiowano z:
http://msdn.microsoft.com/en-us/library/scsyfw1d%28v=vs.71%29.aspx
Rozważ użycie klas abstrakcyjnych, jeśli któreś z poniższych stwierdzeń dotyczy Twojej sytuacji:
Rozważ użycie interfejsów, jeśli któreś z poniższych stwierdzeń dotyczy Twojej sytuacji:
Odpowiedzi różnią się w zależności od języka. Na przykład w Javie klasa może implementować (dziedziczyć) wiele interfejsów, ale dziedziczyć tylko z jednej klasy abstrakcyjnej. Interfejsy zapewniają większą elastyczność. Ale nie jest to prawdą w C ++.
Dla mnie w wielu przypadkach korzystałbym z interfejsów. Ale w niektórych przypadkach wolę zajęcia abstrakcyjne.
Klasy w OO zasadniczo odnoszą się do wdrażania. Korzystam z klas abstrakcyjnych, gdy chcę wymusić pewne szczegóły implementacji dla dzieci, w przeciwnym razie korzystam z interfejsów.
Oczywiście klasy abstrakcyjne są przydatne nie tylko w wymuszaniu implementacji, ale także w dzieleniu się pewnymi szczegółowymi szczegółami wśród wielu powiązanych klas.
Użyj klasy abstrakcyjnej, jeśli chcesz podać podstawowe implementacje.
w java możesz odziedziczyć po jednej (abstrakcyjnej) klasie, aby „zapewnić” funkcjonalność i możesz zaimplementować wiele interfejsów, aby „zapewnić” funkcjonalność
Czysto na podstawie dziedziczenia użyłbyś abstraktu, w którym wyraźnie definiujesz potomka, abstrakcyjne relacje (tj. Zwierzę-> kot) i / lub wymaga dziedziczenia właściwości wirtualnych lub niepublicznych, zwłaszcza stanu wspólnego (którego interfejsy nie mogą obsługiwać ).
Powinieneś starać się faworyzować kompozycję (poprzez wstrzykiwanie zależności) nad dziedziczeniem, o ile możesz, i zauważ, że interfejsy będące umowami wspierają testowanie jednostkowe, rozdzielanie problemów i (w zależności od języka) wielokrotne dziedziczenie w sposób, w jaki Abstrakty nie mogą.
Ciekawym miejscem, w którym interfejsy wypadają lepiej niż klasy abstrakcyjne, jest potrzeba dodania dodatkowej funkcjonalności do grupy (powiązanych lub niepowiązanych) obiektów. Jeśli nie możesz dać im podstawowej klasy abstrakcyjnej (np. Są sealed
lub mają już element nadrzędny), możesz zamiast tego dać im fikcyjny (pusty) interfejs, a następnie po prostu napisać metody rozszerzenia dla tego interfejsu.
Może to być bardzo trudny telefon ...
Jeden wskaźnik, który mogę podać: Obiekt może implementować wiele interfejsów, podczas gdy obiekt może odziedziczyć tylko jedną klasę bazową (w nowoczesnym języku OO, takim jak c #, wiem, że C ++ ma wielokrotne dziedziczenie - ale czy to nie jest obrzydzone?)
Klasa abstrakcyjna może mieć implementacje.
Interfejs nie ma implementacji, po prostu definiuje rodzaj umowy.
Mogą występować również różnice zależne od języka: na przykład C # nie ma wielokrotnego dziedziczenia, ale wiele interfejsów można zaimplementować w klasie.
Podstawowa reguła kciuka: dla „Rzeczowników” użyj klasy Abstrakcja, a dla „Czasowników” użyj interfejsu
Np .: car
jest klasą abstrakcyjną i drive
możemy zrobić z niej interfejs.
drive
w samochodzie - jest to klasa abstrakcyjna.