Biorąc pod uwagę średnio duże oprogramowanie z architekturą n-warstwową i iniekcją zależności, z przyjemnością mogę powiedzieć, że obiekt należący do warstwy może zależeć od obiektów z niższych warstw, ale nigdy od obiektów z wyższych warstw.
Ale nie jestem pewien, co sądzić o obiektach zależnych od innych obiektów tej samej warstwy.
Jako przykład załóżmy aplikację z trzema warstwami i kilkoma obiektami, takimi jak ta na obrazie. Oczywiście zależności odgórne (zielone strzałki) są w porządku, oddolne (czerwona strzałka) nie są w porządku, ale co z zależnościami w tej samej warstwie (żółta strzałka)?
Nie licząc zależności cyklicznej, jestem ciekawy każdego innego problemu, jaki mógłby się pojawić i tego, jak bardzo naruszona jest architektura warstwowa w tym przypadku.
źródło
Odpowiedzi:
Tak, obiekty w jednej warstwie mogą mieć między sobą bezpośrednie zależności, czasem nawet cykliczne - to właśnie sprawia, że podstawowa różnica w stosunku do dozwolonych zależności między obiektami w różnych warstwach, gdzie albo nie są dozwolone bezpośrednie zależności, albo tylko ścisła zależność kierunek .
Nie oznacza to jednak, że powinni oni mieć takie zależności w sposób arbitralny. To zależy od tego, co reprezentują twoje warstwy, jak duży jest system i jaka powinna być odpowiedzialność części. Zauważ, że „architektura warstwowa” jest niejasnym terminem, istnieje ogromna różnorodność tego, co tak naprawdę oznacza w różnych systemach.
Załóżmy na przykład, że masz „system warstwowy” z warstwą bazy danych, warstwą biznesową i warstwą interfejsu użytkownika (UI). Powiedzmy, że warstwa interfejsu użytkownika zawiera co najmniej kilkadziesiąt różnych klas okien dialogowych.
Można wybrać projekt, w którym żadna z klas okien dialogowych nie zależy bezpośrednio od innej klasy okien dialogowych. Można wybrać projekt, w którym istnieją „główne okna dialogowe” i „podrzędne okna dialogowe”, a istnieją tylko bezpośrednie zależności od „głównych” do „podrzędnych” okien dialogowych. Lub można preferować projekt, w którym dowolna istniejąca klasa interfejsu użytkownika może wykorzystywać / ponownie wykorzystywać dowolną inną klasę interfejsu użytkownika z tej samej warstwy.
Są to wszystkie możliwe wybory projektowe, być może mniej lub bardziej rozsądne w zależności od typu budowanego systemu, ale żaden z nich nie powoduje, że „warstwowanie” systemu jest nieprawidłowe.
źródło
Szczerze mówiąc, nie sądzę, że powinieneś się z tym czuć komfortowo. Mając do czynienia z czymkolwiek innym niż trywialnym systemem, starałbym się, aby wszystkie warstwy zawsze zależały tylko od abstrakcji z innych warstw; zarówno niższe, jak i wyższe.
Na przykład
Obj 1
nie powinno zależeć odObj 3
. Powinien być zależny np.IObj 3
I powinien zostać poinformowany, z jaką implementacją tej abstrakcji ma pracować w czasie wykonywania. Rzecz polegająca na mówieniu powinna być niezwiązana z żadnym poziomem, ponieważ jej zadaniem jest mapowanie tych zależności. Może to być kontener IoC, niestandardowy kod wywoływany np. Przez to,main
że używa czystej DI. Lub po naciśnięciu może to być nawet lokalizator usług. Niezależnie od tego, zależności między warstwami nie istnieją, dopóki ta rzecz nie zapewni mapowania.Twierdzę, że jest to jedyny czas, kiedy powinieneś mieć bezpośrednie zależności. Jest to część wewnętrznego działania tej warstwy i można ją zmienić bez wpływu na inne warstwy. Więc to nie jest szkodliwe połączenie.
źródło
Spójrzmy na to praktycznie
Obj 3
teraz wieObj 4
, że istnieje. Więc co? Dlaczego nas to obchodzi?OK, ale czy nie wszystkie abstrakcje obiektów?
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
wieObj 4
, że istnieje. Ale czyObj 3
wie, czyObj 4
jest konkretny?To właśnie dlatego tak miło NIE rozprzestrzeniać się
new
wszędzie. JeśliObj 3
nie wie, czyObj 4
jest konkretny, prawdopodobnie dlatego, że go nie stworzył, to jeśli wślizgniesz się później i zmienisz wObj 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 przypadkowoObj 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
iObj 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ę”.
źródło
Oprócz powyższych odpowiedzi, myślę, że może pomóc ci spojrzeć na to z różnych punktów widzenia.
Na przykład z punktu widzenia reguły zależności . DR jest regułą sugerowaną przez Roberta C. Martina dla jego słynnej „ Czystej architektury” .
To mówi
Przez politykę wyższego poziomu rozumie wyższy poziom abstrakcji. Komponenty, które przeciekają szczegóły implementacji, takie jak na przykład interfejsy lub klasy abstrakcyjne w przeciwieństwie do konkretnych klas lub struktur danych.
Chodzi o to, że reguła nie jest ograniczona do zależności między warstwami. Wskazuje tylko na zależność między fragmentami kodu, niezależnie od lokalizacji lub warstwy, do której należą.
Więc nie, nie ma nic z natury złego w zależnościach między elementami tej samej warstwy. Zależność można jednak nadal wdrożyć, aby przekazać zasadę stabilnej zależności .
Innym punktem widzenia jest SRP.
Oddzielenie jest naszym sposobem na przełamanie szkodliwych zależności i przekazanie informacji za pomocą najlepszych praktyk, takich jak inwersja zależności (IoC). Jednak te elementy, które dzielą powody zmiany, nie podają powodów odłączenia, ponieważ elementy z tym samym powodem zmiany zmienią się w tym samym czasie (bardzo prawdopodobne) i zostaną wdrożone w tym samym czasie. Jeśli jest to sprawa między
Obj3
aObj4
następnie, po raz kolejny, nie ma nic złego.źródło