Rozważmy na przykład, że mam klasę dla innych klas do rozszerzenia:
public class LoginPage {
public String userId;
public String session;
public boolean checkSessionValid() {
}
}
i niektóre podklasy:
public class HomePage extends LoginPage {
}
public class EditInfoPage extends LoginPage {
}
W rzeczywistości podklasa nie ma żadnych metod przesłonięcia, również nie uzyskałbym dostępu do strony głównej w sposób ogólny, tj .: nie zrobiłbym czegoś takiego:
for (int i = 0; i < loginPages.length; i++) {
loginPages[i].doSomething();
}
Chcę tylko ponownie użyć strony logowania. Ale zgodnie z https://stackoverflow.com/a/53354 powinienem tutaj preferować kompozycję, ponieważ nie potrzebuję interfejsu LoginPage, więc nie używam tutaj dziedziczenia:
public class HomePage {
public LoginPage loginPage;
}
public class EditInfoPage {
public LoginPage loginPage;
}
ale problem pojawia się tutaj, w nowej wersji kod:
public LoginPage loginPage;
duplikuje się po dodaniu nowej klasy. A jeśli LoginPage wymaga narzędzia ustawiającego i pobierającego, należy skopiować więcej kodów:
public LoginPage loginPage;
private LoginPage getLoginPage() {
return this.loginPage;
}
private void setLoginPage(LoginPage loginPage) {
this.loginPage = loginPage;
}
Więc moje pytanie brzmi: czy „kompozycja nad spadkiem” narusza „suchą zasadę”?
inheritance
composition
dry
ocomfd
źródło
źródło
extends LoginPage
każdym miejscu. SZACH MAT!LoginPage
dekoratorem. Nigdy więcej duplikacji, po prostu prostepage = new LoginPage(new EditInfoPage())
i gotowe. Lub wykorzystujesz zasadę otwartego zamknięcia, aby moduł uwierzytelniania można było dynamicznie dodawać do dowolnej strony. Istnieje wiele sposobów radzenia sobie z powielaniem kodu, głównie polegających na znalezieniu nowej abstrakcji.LoginPage
jest prawdopodobnie złą nazwą, co chcesz upewnić się, że użytkownik jest uwierzytelniany podczas przeglądania tej strony, podczas gdy jest przekierowywany doLoginPage
lub wyświetla odpowiedni komunikat o błędzie, jeśli nie jest.Odpowiedzi:
Czekaj, obawiasz się, że to się powtórzy
w dwóch miejscach narusza SUSZENIE? Zgodnie z tą logiką
może teraz istnieć tylko w jednym obiekcie w całej bazie kodu. Bleh
SUSZENIE to dobra rzecz, o której należy pamiętać, ale daj spokój. Oprócz
jest duplikowany w alternatywie, więc nawet bycie analnym na temat DRY nie ma sensu.
Prawidłowe obawy związane z DRY koncentrują się na identycznym zachowaniu potrzebnym w wielu miejscach, które są definiowane w wielu miejscach, tak że potrzeba zmiany tego zachowania kończy się koniecznością zmiany w wielu miejscach. Podejmuj decyzje w jednym miejscu, a będziesz musiał je zmienić tylko w jednym miejscu. Nie oznacza to, że tylko jeden obiekt może kiedykolwiek zawierać odniesienie do Twojej strony logowania.
Nie należy ślepo śledzić SUSZENIA. Jeśli duplikujesz, ponieważ kopiowanie i wklejanie jest łatwiejsze niż wymyślenie dobrej metody lub nazwy klasy, prawdopodobnie jesteś w błędzie.
Ale jeśli chcesz umieścić ten sam kod w innym miejscu, ponieważ to inne miejsce podlega innej odpowiedzialności i prawdopodobnie będzie wymagać niezależnej zmiany, prawdopodobnie rozsądnie jest złagodzić wymuszanie DRY i pozwolić temu identycznemu zachowaniu mieć inną tożsamość . To ten sam sposób myślenia, który dotyczy zakazania magicznych liczb.
DRY to nie tylko wygląd kodu. Chodzi o to, aby nie rozpowszechniać szczegółów pomysłu za pomocą bezmyślnego powtarzania, zmuszając opiekunów do naprawy rzeczy przy użyciu bezmyślnego powtarzania. To kiedy próbujesz sobie powiedzieć, że bezmyślne powtarzanie jest tylko twoją konwencją, sprawy idą naprawdę źle.
To, co myślę, że tak naprawdę narzekasz, nazywa się kodem typu „kocioł”. Tak, użycie kompozycji zamiast dziedziczenia wymaga kodu typu „kocioł”. Nic nie zostaje ujawnione za darmo, musisz napisać kod, który go ujawnia. Z tą płytą główną jest elastyczność stanu, możliwość zawężenia odsłoniętego interfejsu, nadania rzeczom różnych nazw, które są odpowiednie dla ich poziomu abstrakcji, dobrej pośredniej oli, a używasz tego, z czego jesteś złożony z zewnątrz, a nie wewnątrz, więc masz do czynienia z normalnym interfejsem.
Ale tak, to dużo dodatkowego pisania na klawiaturze. Tak długo, jak mogę zapobiec problemowi jo-jo podskakiwania w górę i w dół stosu dziedziczenia, gdy czytam kod, warto.
Teraz nie chodzi o to, że odmawiam kiedykolwiek dziedziczenia. Jednym z moich ulubionych zastosowań jest nadawanie wyjątkom nowych nazw:
źródło
int x;
może istnieć nie więcej niż zero razy w bazie koduint a; int b; int x;
, będę naciskać, aby usunąć Ciebie.Powszechnym nieporozumieniem z zasadą DRY jest to, że wiąże się to z powtarzaniem wierszy kodu. Zasada DRY to: „Każda wiedza musi mieć jedną, jednoznaczną, autorytatywną reprezentację w systemie” . Chodzi o wiedzę, a nie kod.
LoginPage
wie, jak narysować stronę do logowania. GdybyEditInfoPage
wiedział, jak to zrobić, byłoby to naruszeniem. W tymLoginPage
poprzez kompozycji jest nie w jakikolwiek sposób naruszenie zasady DRY.Zasada DRY jest prawdopodobnie najbardziej nadużywaną zasadą w inżynierii oprogramowania i zawsze należy ją traktować nie jako zasadę nie powielania kodu, ale jako zasadę nie powielania wiedzy w dziedzinie abstrakcyjnej. W rzeczywistości, w wielu przypadkach, jeśli zastosujesz poprawnie OSUSZANIE, będziesz kopiować kod , i to niekoniecznie jest złą rzeczą.
źródło
Krótka odpowiedź: tak, robi - w pewnym stopniu akceptowalnym stopniu.
Na pierwszy rzut oka dziedziczenie może czasami zaoszczędzić ci niektóre wiersze kodu, ponieważ powoduje, że „moja klasa ponownego wykorzystywania będzie zawierać wszystkie publiczne metody i atrybuty w sposób 1: 1”. Jeśli więc w składniku znajduje się lista 10 metod, nie trzeba powtarzać ich w kodzie w odziedziczonej klasie. Gdy w scenariuszu kompozycyjnym 9 z tych 10 metod powinno zostać publicznie ujawnionych za pomocą komponentu służącego do ponownego użycia, wówczas należy spisać 9 wywołań delegowania, a pozostawiając jedno z pozostałych, nie można tego obejść.
Dlaczego to jest tolerowane? Spójrz na metody replikowane w scenariuszu kompozycji - metody te delegują wyłącznie wywołania do interfejsu komponentu, więc nie zawierają żadnej logiki.
Istotą zasady DRY jest unikanie dwóch miejsc w kodzie, w których kodowane są te same reguły logiczne - ponieważ gdy te reguły logiczne ulegną zmianie, w kodzie innym niż DRY łatwo jest dostosować jedno z tych miejsc i zapomnieć o drugim, co wprowadza błąd.
Ponieważ jednak delegowanie połączeń nie zawiera logiki, zwykle nie podlegają one takiej zmianie, więc nie powodują prawdziwego problemu, gdy „wolą kompozycję niż dziedziczenie”. I nawet jeśli interfejs komponentu ulegnie zmianie, co może spowodować zmiany formalne we wszystkich klasach korzystających z komponentu, w skompilowanym języku kompilator powie nam, kiedy zapomnimy zmienić jednego z wywołujących.
Uwaga do twojego przykładu: nie wiem jak wyglądają twoje
HomePage
i twojeEditInfoPage
, ale jeśli mają one funkcję logowania, aHomePage
(lub anEditInfoPage
) jestLoginPage
, to dziedziczenie może być tutaj właściwym narzędziem. Mniej dyskusyjny przykład, w którym kompozycja będzie lepszym narzędziem w bardziej oczywisty sposób, prawdopodobnie by to wyjaśniło.Zakładając, że nie ma ani
HomePage
nieEditInfoPage
jestLoginPage
, a ktoś chce ponownie użyć tego drugiego, jak pisałeś, jest całkiem prawdopodobne, że wymaga się tylko niektórych częściLoginPage
, a nie wszystkiego. W takim przypadku może być lepsze podejście niż użycie kompozycji w pokazany sposóbwyodrębnij część wielokrotnego użytku
LoginPage
do własnego komponentuponowne wykorzystanie tego elementu w
HomePage
iEditInfoPage
tak samo jest teraz używany wewnątrzLoginPage
W ten sposób zazwyczaj stanie się o wiele bardziej jasne, dlaczego i kiedy kompozycja nad dziedziczeniem jest właściwym podejściem.
źródło