Spójrzmy na to praktycznie
Obj 3
teraz wie Obj 4
, że istnieje. Więc co? Dlaczego nas to obchodzi?
DIP mówi
„Moduły wysokiego poziomu nie powinny zależeć od modułów niskiego poziomu. Oba powinny zależeć od abstrakcji”.
OK, ale czy nie wszystkie abstrakcje obiektów?
DIP mówi również
„Abstrakcje nie powinny zależeć od szczegółów. Szczegóły powinny zależeć od abstrakcji”.
OK, ale jeśli mój obiekt jest odpowiednio zamknięty, czy to nie ukrywa żadnych szczegółów?
Niektórzy ludzie lubią ślepo nalegać, aby każdy obiekt wymagał interfejsu słów kluczowych. Nie jestem jednym z nich. Lubię ślepo nalegać, że jeśli nie zamierzasz ich teraz używać, potrzebujesz planu, aby poradzić sobie z potrzebowaniem czegoś takiego jak później.
Jeśli Twój kod jest w pełni refaktoryzujący w każdej wersji, możesz po prostu wyodrębnić interfejsy później, jeśli są potrzebne. Jeśli opublikowałeś kod, którego nie chcesz rekompilować, i chciałbyś rozmawiać przez interfejs, potrzebujesz planu.
Obj 3
wie Obj 4
, że istnieje. Ale czy Obj 3
wie, czy Obj 4
jest konkretny?
To właśnie dlatego tak miło NIE rozprzestrzeniać się new
wszędzie. Jeśli Obj 3
nie wie, czy Obj 4
jest konkretny, prawdopodobnie dlatego, że go nie stworzył, to jeśli wślizgniesz się później i zmienisz w Obj 4
abstrakcyjną klasę Obj 3
, nie będzie to miało znaczenia.
Jeśli możesz to zrobić, to przez Obj 4
cały czas była całkowicie abstrakcyjna. Jedyną rzeczą, która tworzy interfejs między nimi od samego początku, jest pewność, że ktoś nie doda przypadkowo Obj 4
konkretnego kodu, który jest teraz konkretny. Chronieni konstruktorzy mogą zmniejszyć to ryzyko, ale prowadzi to do kolejnego pytania:
Czy Obj 3 i Obj 4 są w tym samym pakiecie?
Obiekty są często grupowane w jakiś sposób (pakiet, przestrzeń nazw itp.). Po zgrupowaniu mądrze zmień bardziej prawdopodobne skutki w obrębie grupy, a nie między grupami.
Lubię grupować według funkcji. Jeśli Obj 3
i Obj 4
należą do tej samej grupy i warstwy, jest bardzo mało prawdopodobne, że opublikujesz jedną i nie będziesz chciał jej refaktoryzować, a będziesz musiał zmienić tylko drugą. Oznacza to, że te obiekty rzadziej skorzystają z umieszczenia między nimi abstrakcji, zanim będzie to wyraźnie potrzebne.
Jeśli jednak przekraczasz granicę grupy, dobrym pomysłem jest, aby obiekty po obu stronach zmieniały się niezależnie.
Powinno być tak proste, ale niestety zarówno Java, jak i C # dokonały niefortunnych wyborów, które to komplikują.
W języku C # tradycją jest nazywanie każdego interfejsu słowa kluczowego I
prefiksem. To zmusza klientów do WIEDZ, że rozmawiają z interfejsem słów kluczowych. To nie zgadza się z planem refaktoryzacji.
W Javie tradycyjnie stosuje się lepszy wzorzec nazewnictwa: FooImple implements Foo
pomaga to jednak tylko na poziomie kodu źródłowego, ponieważ Java kompiluje interfejsy słów kluczowych do innego pliku binarnego. Oznacza to, że jeśli zmienisz fakturę Foo
z konkretnych na abstrakcyjne klientów, które nie wymagają zmiany jednego znaku kodu, nadal trzeba go skompilować.
To właśnie BŁĘDY w tych konkretnych językach powstrzymują ludzi przed odkładaniem formalnej abstrakcji, dopóki naprawdę jej nie potrzebują. Nie powiedziałeś, jakiego języka używasz, ale rozumiesz, że niektóre języki po prostu nie mają tych problemów.
Nie powiedziałeś, jakiego języka używasz, więc zachęcam cię do dokładnej analizy języka i sytuacji, zanim zdecydujesz, że będą to wszędzie interfejsy słów kluczowych.
Kluczową rolę odgrywa tutaj zasada YAGNI. Ale tak też „Proszę, postaraj się utrudnić mi strzelenie w stopę”.