Jeśli założymy, że nie jest pożądane, aby klasa podstawowa była czystą klasą interfejsu i przy użyciu 2 poniższych przykładów, co jest lepszym podejściem przy użyciu abstrakcyjnej lub wirtualnej definicji klasy metody?
Zaletą „abstrakcyjnej” wersji jest to, że prawdopodobnie wygląda ona na czystszą i zmusza klasę pochodną do podania, miejmy nadzieję, znaczącej implementacji.
Zaletą wersji „wirtualnej” jest to, że można ją łatwo pobrać z innych modułów i wykorzystać do testowania bez dodawania zestawu podstawowych struktur, jakich wymaga wersja abstrakcyjna.
Wersja abstrakcyjna:
public abstract class AbstractVersion
{
public abstract ReturnType Method1();
public abstract ReturnType Method2();
.
.
public abstract ReturnType MethodN();
//////////////////////////////////////////////
// Other class implementation stuff is here
//////////////////////////////////////////////
}
Wersja wirtualna:
public class VirtualVersion
{
public virtual ReturnType Method1()
{
return ReturnType.NotImplemented;
}
public virtual ReturnType Method2()
{
return ReturnType.NotImplemented;
}
.
.
public virtual ReturnType MethodN()
{
return ReturnType.NotImplemented;
}
//////////////////////////////////////////////
// Other class implementation stuff is here
//////////////////////////////////////////////
}
return ReturnType.NotImplemented
? Poważnie? Jeśli nie możesz odrzucić niezaimplementowanego typu w czasie kompilacji (możesz; użyj metod abstrakcyjnych), przynajmniej rzuć wyjątek.Odpowiedzi:
Mój głos, gdybym konsumował wasze rzeczy, byłby za metodami abstrakcyjnymi. To idzie w parze z „wczesnym niepowodzeniem”. Dodanie wszystkich metod może być utrudnieniem w momencie deklaracji (choć każde przyzwoite narzędzie refaktoryzacyjne zrobi to szybko), ale przynajmniej wiem, co to jest problem natychmiast i go naprawię. Wolę to, niż debugowanie 6 miesięcy i zmian 12 osób później, aby zobaczyć, dlaczego nagle otrzymujemy niezaimplementowany wyjątek.
źródło
Wersja wirtualna jest podatna na błędy i semantycznie niepoprawna.
Streszczenie mówi „ta metoda nie jest tu zaimplementowana. Musisz ją zaimplementować, aby ta klasa działała”
Virtual mówi „Mam domyślną implementację, ale możesz mnie zmienić, jeśli potrzebujesz”
Jeśli Twoim ostatecznym celem jest testowalność, interfejsy są zwykle najlepszą opcją. (ta klasa robi x, a ta klasa to ax). Może być konieczne podzielenie klas na mniejsze elementy, aby działało to dobrze.
źródło
To zależy od wykorzystania twojej klasy.
Jeśli metody mają rozsądną implementację „pustą”, masz wiele metod i często zastępujesz tylko kilka z nich, wtedy użycie
virtual
metod ma sens. Na przykładExpressionVisitor
jest realizowany w ten sposób.W przeciwnym razie uważam, że powinieneś użyć
abstract
metod.W idealnym przypadku nie powinieneś mieć metod, które nie są zaimplementowane, ale w niektórych przypadkach jest to najlepsze podejście. Ale jeśli zdecydujesz się to zrobić, takie metody powinny rzucać
NotImplementedException
, a nie zwracać jakąś specjalną wartość.źródło
Sugerowałbym, aby ponownie rozważyć zdefiniowanie oddzielnego interfejsu, który implementuje klasa podstawowa, a następnie zastosować podejście abstrakcyjne.
Kod obrazowania taki jak ten:
W ten sposób rozwiązuje się następujące problemy:
Dzięki temu, że cały kod korzystający z obiektów pochodzących z AbstractVersion można teraz zaimplementować w celu otrzymania interfejsu IVersion, oznacza to, że można je łatwiej testować jednostkowo.
Wersja 2 produktu może następnie zaimplementować interfejs IVersion2, aby zapewnić dodatkową funkcjonalność bez naruszania istniejącego kodu klienta.
na przykład.
Warto również przeczytać o odwróceniu zależności, aby ta klasa nie zawierała zakodowanych na stałe zależności, które uniemożliwiają skuteczne testowanie jednostek.
źródło
Wstrzykiwanie zależności zależy od interfejsów. Oto krótki przykład. Class Student ma funkcję o nazwie CreateStudent, która wymaga parametru implementującego interfejs „IReporting” (z metodą ReportAction). Po utworzeniu ucznia wywołuje ReportAction dla konkretnego parametru klasy. Jeśli system jest skonfigurowany do wysyłania wiadomości e-mail po utworzeniu ucznia, wysyłamy konkretną klasę, która wysyła wiadomość e-mail w implementacji ReportAction, lub możemy wysłać inną konkretną klasę, która wysyła dane wyjściowe do drukarki w implementacji ReportAction. Idealne do ponownego użycia kodu.
źródło