Istnieją dwa sposoby wykorzystania abstrakcyjnych klas bazowych.
Specjalizujesz się w swoim abstrakcyjnym obiekcie, ale wszyscy klienci będą korzystać z pochodnej klasy poprzez interfejs podstawowy.
Używasz abstrakcyjnej klasy bazowej, aby wykluczyć duplikację obiektów w twoim projekcie, a klienci używają konkretnych implementacji poprzez swoje własne interfejsy.!
Rozwiązanie dla 1 - wzorzec strategii
Jeśli masz pierwszą sytuację, to faktycznie masz interfejs zdefiniowany przez metody wirtualne w klasie abstrakcyjnej, którą implementują twoje klasy pochodne.
Powinieneś rozważyć uczynienie tego prawdziwym interfejsem, zmianę swojej klasy abstrakcyjnej na konkretną i wzięcie instancji tego interfejsu w jego konstruktorze. Twoje pochodne klasy stają się implementacjami tego nowego interfejsu.
Oznacza to, że możesz teraz przetestować swoją poprzednio abstrakcyjną klasę za pomocą fałszywej instancji nowego interfejsu i każdej nowej implementacji za pośrednictwem teraz publicznego interfejsu. Wszystko jest proste i możliwe do przetestowania.
Rozwiązanie dla 2
Jeśli masz drugą sytuację, twoja klasa abstrakcyjna działa jako klasa pomocnicza.
Spójrz na funkcje, które zawiera. Sprawdź, czy którykolwiek z nich można zepchnąć na obiekty, którymi manipulujesz, aby zminimalizować to powielanie. Jeśli nadal masz coś do zrobienia, spójrz na uczynienie z niego klasy pomocniczej, którą Twoja konkretna implementacja przyjmuje w swoim konstruktorze i usuń ich klasę podstawową.
To znowu prowadzi do konkretnych klas, które są proste i łatwe do przetestowania.
Z zasady
Preferuj złożoną sieć prostych obiektów nad prostą siecią złożonych obiektów.
Kluczem do rozszerzalnego kodu testowalnego są małe bloki konstrukcyjne i niezależne okablowanie.
Zaktualizowano: Jak obsługiwać mieszanki obu?
Możliwe jest, aby klasa podstawowa pełniła obie te role ... tzn .: ma interfejs publiczny i chroni metody pomocnicze. W takim przypadku można rozdzielić metody pomocnicze na jedną klasę (scenariusz 2) i przekonwertować drzewo dziedziczenia na wzorzec strategii.
Jeśli okaże się, że masz metody, które bezpośrednio implementuje klasa podstawowa, a inne są wirtualne, nadal możesz przekonwertować drzewo dziedziczenia na wzorzec strategii, ale uznałbym to również za dobry wskaźnik, że obowiązki nie są odpowiednio wyrównane i może potrzebujesz refaktoryzacji.
Aktualizacja 2: Klasy abstrakcyjne jako odskocznia (2014/06/12)
Pewnego dnia miałem sytuację, w której użyłem abstrakcji, więc chciałbym dowiedzieć się, dlaczego.
Mamy standardowy format dla naszych plików konfiguracyjnych. To narzędzie ma 3 pliki konfiguracyjne w tym formacie. Chciałem silnie wpisanej klasy dla każdego pliku ustawień, więc poprzez wstrzyknięcie zależności klasa może poprosić o ustawienia, na których mu zależy.
Zaimplementowałem to, mając abstrakcyjną klasę bazową, która wie, jak analizować formaty plików ustawień i klasy pochodne, które ujawniały te same metody, ale zawierały lokalizację pliku ustawień.
Mógłbym napisać „SettingsFileParser”, który 3 klasy owinęły, a następnie przekazały do klasy podstawowej, aby ujawnić metody dostępu do danych. Nie zdecydowałem się tego jeszcze zrobić, ponieważ doprowadziłoby to do powstania 3 klas pochodnych z większą ilością kodu delegacji niż cokolwiek innego.
Jednak ... wraz z ewolucją tego kodu, a odbiorcy każdej z tych klas ustawień stają się coraz wyraźniejsi. Każdy z ustawień poprosi o pewne ustawienia i przekształci je w jakiś sposób (ponieważ ustawienia są tekstem, mogą zawinąć je w obiekty i przekonwertować na liczby itp.). Gdy to się stanie, zacznę wyodrębniać tę logikę do metod manipulacji danymi i wypychać je z powrotem do silnie wpisanych klas ustawień. Doprowadzi to do interfejsu wyższego poziomu dla każdego zestawu ustawień, który ostatecznie nie będzie już świadomy, że ma do czynienia z „ustawieniami”.
W tym momencie mocno wpisane klasy ustawień nie będą już potrzebowały metod „getter”, które ujawniają bazową implementację „ustawień”.
W tym momencie nie chciałbym już, aby ich interfejs publiczny zawierał metody akcesora ustawień; więc zmienię tę klasę, aby hermetyzowała klasę parsera ustawień zamiast z niej czerpać.
Klasa Abstract jest zatem: sposobem na uniknięcie w tej chwili kodu delegacji oraz znacznikiem w kodzie, który przypomina mi o zmianie projektu później. Być może nigdy się do tego nie dostanę, więc może żyć długo ... tylko kod może to stwierdzić.
Uważam, że jest to prawdą w przypadku każdej reguły ... jak „brak metod statycznych” lub „brak metod prywatnych”. Wskazują zapach w kodzie ... i to dobrze. Pomaga ci szukać abstrakcji, którą przegapiłeś ... i pozwala w międzyczasie nadal dostarczać wartość klientowi.
Wyobrażam sobie takie reguły, które definiują krajobraz, w którym w dolinach żyje możliwy do utrzymania kod. Gdy dodajesz nowe zachowanie, to jest jak deszcz lądujący na twoim kodzie. Początkowo umieszczasz go gdziekolwiek wyląduje .. następnie refaktoryzujesz, aby siły dobrego projektu popchnęły zachowanie, aż wszystko skończy się w dolinach.