Czy problematyczna jest zależność między obiektami tej samej warstwy w warstwowej architekturze oprogramowania?

12

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)?

wprowadź opis zdjęcia tutaj

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.

bracco23
źródło
Jeśli nie masz cykli, w warstwie z połączeniami horysontal istnieje podział na podwarstwy, który ma tylko zależności między warstwami
max630

Odpowiedzi:

10

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.

Doktor Brown
źródło
Kontynuując przykład interfejsu użytkownika, jakie są zalety i wady posiadania wewnętrznych zależności? Widzę, że ułatwia to ponowne użycie i wprowadza cykliczne zależności, co może stanowić problem w zależności od zastosowanej metody DI. Coś jeszcze?
bracco23
@ bracco23: zalety i wady posiadania „wewnętrznych” zależności są takie same, jak zalety i wady posiadania dowolnych zależności. „Wady” sprawiają, że komponenty są trudniejsze do ponownego użycia i trudniejsze do przetestowania, szczególnie w oderwaniu od innych komponentów. Plusy: wyraźne zależności sprawiają, że rzeczy wymagające spójności są łatwiejsze w użyciu, zrozumieniu, zarządzaniu i testowaniu.
Doc Brown
14

Z przyjemnością mogę powiedzieć, że obiekt należący do warstwy może zależeć od obiektów z niższych warstw

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 1nie powinno zależeć od Obj 3. Powinien być zależny np. IObj 3I 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.

Ale nie jestem pewien, co sądzić o obiektach zależnych od innych obiektów tej samej warstwy.

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.

David Arno
źródło
Dziękuję za odpowiedź. Tak, warstwa nie powinna odsłaniać obiektów, ale interfejsy, wiem o tym, ale dla uproszczenia pominęłam to.
bracco23
4

Spójrzmy na to praktycznie

wprowadź opis zdjęcia tutaj

Obj 3teraz 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 3wie Obj 4, że istnieje. Ale czy Obj 3wie, czy Obj 4jest konkretny?

To właśnie dlatego tak miło NIE rozprzestrzeniać się newwszędzie. Jeśli Obj 3nie wie, czy Obj 4jest konkretny, prawdopodobnie dlatego, że go nie stworzył, to jeśli wślizgniesz się później i zmienisz w Obj 4abstrakcyjną klasę Obj 3, nie będzie to miało znaczenia.

Jeśli możesz to zrobić, to przez Obj 4cał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 4konkretnego 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 3i Obj 4należą 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 Iprefiksem. 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 Foopomaga 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ę Fooz 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ę”.

candied_orange
źródło
0

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

Zależności kodu źródłowego muszą być skierowane tylko do wewnątrz, w kierunku zasad wyższego poziomu.

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 Obj3a Obj4następnie, po raz kolejny, nie ma nic złego.

Laiv
źródło