Czy przechodzenie instancji przez kilka warstw jest złą praktyką?

60

Podczas projektowania programu często dochodzę do momentu, w którym muszę przekazywać instancje obiektów przez kilka klas. Na przykład, jeśli mam kontroler, który ładuje plik audio, a następnie przekazuje go do odtwarzacza, a odtwarzacz przekazuje go do odtwarzacza Runner, który przekazuje go gdzie indziej itp. Wygląda to trochę źle, ale nie umieć tego uniknąć. Czy może to zrobić?

EDYCJA: Może przykład odtwarzacza nie jest najlepszy, ponieważ mógłbym załadować plik później, ale w innych przypadkach to nie działa.

Puckl
źródło

Odpowiedzi:

54

Jak już wspomnieli inni, niekoniecznie jest to zła praktyka, ale należy zwrócić uwagę, aby nie przerywać oddzielania warstw między obawy i przekazywać specyficzne dla warstw instancje między warstwami. Na przykład:

  • Obiekty bazy danych nigdy nie powinny być przekazywane do wyższych warstw. Widziałem programy wykorzystujące klasę DataNET Adapter .NET, klasę dostępu do bazy danych i przekazującą ją do warstwy interfejsu użytkownika, zamiast używania DataAdapter w DAL, tworzenia DTO lub zestawu danych i przekazywania tego. Dostęp do bazy danych jest domeną DAL.
  • Obiekty interfejsu użytkownika powinny oczywiście być ograniczone do warstwy interfejsu użytkownika. Znowu widziałem, że zostało to naruszone, zarówno z ListBoxami wypełnionymi danymi użytkownika przekazywanymi do warstwy BL, zamiast tablicy / DTO jej treści, jak i (moim ulubionym), klasą DAL, która pobierała dane hierarchiczne z DB, zamiast zwracać hierarchiczną strukturę danych, po prostu utworzył i wypełnił obiekt TreeView i przekazał go z powrotem do interfejsu użytkownika, aby dynamicznie dodać go do formularza.

Jeśli jednak przekazywane przez Ciebie instancje to DTO lub same podmioty, prawdopodobnie jest to w porządku.

Avner Shahar-Kashtan
źródło
1
Może to zabrzmieć szokująco, ale w ciemnych wczesnych dniach .NET była to ogólnie zalecana praktyka i prawdopodobnie była lepsza niż większość innych stosów.
Wyatt Barnett
1
Nie zgadzam się. To prawda, że ​​Microsoft poparł praktykę aplikacji jednowarstwowych, w których klient Winforms również uzyskiwał dostęp do DB, a DataAdapter został dodany bezpośrednio do formularza jako niewidzialna kontrola, ale to tylko specyficzna architektura, inna niż konfiguracja N-warstwy OP . Ale w architekturze wielowarstwowej, i tak było w przypadku VB6 / DNA, nawet zanim .NET obiekty DB pozostały w warstwie DB.
Avner Shahar-Kashtan
Aby wyjaśnić: widziałeś osoby uzyskujące bezpośredni dostęp do interfejsu użytkownika (w twoim przykładzie listbox) z ich „warstwy danych”? Nie sądzę, że spotkałem się z takim naruszeniem kodu produkcyjnego .. wow ..
Simon Whitehead
7
@SimonWhitehead Dokładnie. Ludzie, którzy mieli wątpliwości co do rozróżnienia między ListBox i tablicą i używali ListBoxes jako DTO. To był moment, który pomógł mi zrozumieć, jak wiele niewidzialnych założeń nie jestem intuicyjny dla innych.
Avner Shahar-Kashtan
1
@ SimonWhitehead - Tak, widziałem to już w programach VB6 i VB.NET Framework 1.1 i 2.0 i miałem za zadanie utrzymanie takich potworów. Robi się bardzo brzydko, bardzo szybko.
jfrankcarr
15

Ciekawe, że nikt jeszcze nie mówił o Niezmiennych obiektach . Twierdziłbym, że przepuszczanie niezmiennego obiektu przez wszystkie różne warstwy jest w rzeczywistości dobrą rzeczą, zamiast tworzenia wielu krótkotrwałych obiektów dla każdej warstwy.

Na swoim blogu znajduje się kilka świetnych dyskusji na temat niezmienności Erica Lipperta

Z drugiej strony twierdzę, że przekazywanie obiektów zmiennych między warstwami jest złym projektem. Zasadniczo budujesz warstwę z obietnicą, że otaczające ją warstwy nie zmienią jej w sposób, aby złamać kod.

M Afifi
źródło
13

Przekazywanie instancji obiektów jest normalną czynnością. Zmniejsza to potrzebę utrzymywania stanu (tj. Zmiennych instancji) i oddziela kod od kontekstu wykonywania.

Jednym z problemów, które możesz napotkać, jest refaktoryzacja, kiedy musisz zmienić podpisy wielu metod w łańcuchu wywołań w odpowiedzi na zmieniające się wymagania parametrów metody w dolnej części łańcucha. Można to jednak złagodzić za pomocą nowoczesnych narzędzi programistycznych, które pomagają w refaktoryzacji.

dasblinkenlight
źródło
6
Powiedziałbym, że innym problemem, z którym możesz się zmierzyć, jest niezmienność. Pamiętam bardzo kłopotliwy błąd w projekcie, nad którym pracowałem, w którym programista zmodyfikował DTO, nie myśląc o tym, że jego konkretna klasa nie była jedyna z odniesieniem do tego obiektu.
Phil
8

Może niewielki, ale istnieje ryzyko przypisania tego odniesienia gdzieś w jednej z warstw, co może później spowodować zawieszenie odniesienia lub wyciek pamięci.

techfoobar
źródło
Masz rację, ale z terminologii OP („przekazywanie instancji obiektów”) mam wrażenie, że albo przekazuje wartości (nie wskaźniki), albo mówił o środowisku śmieciowym (Java, C #, Python, Go,. ..).
Mohammad Dehghan
7

Jeśli przekazujesz obiekty po prostu dlatego, że jest to potrzebne w odległym obszarze kodu, użycie odwrócenia wzorców projektowych wstrzykiwania kontroli i zależności oraz opcjonalnie odpowiedniego kontenera IoC może dobrze rozwiązać problemy dotyczące przenoszenia instancji obiektów. Użyłem go w średnim projekcie i nigdy więcej nie rozważyłbym napisania dużego fragmentu kodu serwera bez jego użycia.

Steven Schlansker
źródło
Brzmi interesująco, już używam iniekcji konstruktora i myślę, że moje komponenty wysokiego poziomu kontrolują komponenty niskiego poziomu. Jak korzystać z kontenera MKOl, aby uniknąć przenoszenia wystąpień?
Puckl,
Myślę, że znalazłem odpowiedź tutaj: martinfowler.com/articles/injection.html
Puckl
1
Na marginesie, jeśli pracujesz w Javie, Guice jest naprawdę fajny i możesz zawęzić powiązania do takich rzeczy jak żądania, tak aby komponent wysokiego poziomu tworzył zakres i wiązał instancje z odpowiednimi klasami w tym czasie.
Dave
4

Przekazywanie danych przez wiązkę warstw nie jest złą rzeczą, to naprawdę jedyny sposób, w jaki system warstwowy może działać bez naruszania struktury warstwowej. Znakiem są problemy, gdy przekazujesz dane do kilku obiektów na tej samej warstwie, aby osiągnąć swój cel.

Ryathal
źródło
3

Szybka odpowiedź: Nie ma nic złego w przekazywaniu instancji obiektów. Jak również wspomniano, należy pominąć przypisywanie tego odniesienia do wszystkich warstw, potencjalnie powodując wiszące odniesienie lub wycieki pamięci.

W naszych projektach wykorzystujemy tę praktykę do przekazywania DTO (obiektu przesyłania danych) między warstwami i jest to bardzo pomocna praktyka. Ponownie wykorzystujemy również nasze obiekty dto, aby raz bardziej skomplikować budowę, na przykład w celu uzyskania informacji podsumowujących.

EL Yusubov
źródło
3

Jestem przede wszystkim twórcą interfejsu WWW, ale wydaje mi się, że twój intuicyjny dyskomfort może być mniej związany z przekazywaniem instancji, a bardziej z faktem, że masz trochę proceduralne z tym kontrolerem. Czy kontroler powinien pocić wszystkie te szczegóły? Dlaczego odwołuje się nawet do nazwy więcej niż jednego innego obiektu do odtwarzania dźwięku?

W projektowaniu OOP mam tendencję do myślenia w kategoriach tego, co jest wiecznie zielone i co może ulec zmianie. Przedmiotem zmian jest to, co będziesz chciał umieścić w swoich większych obiektach, aby zachować spójne interfejsy, nawet gdy gracze się zmieniają lub dodaje się nowe opcje. Lub też chcesz hurtowo zamienić obiekty audio lub komponenty.

W takim przypadku kontroler musi stwierdzić, że istnieje potrzeba odtworzenia pliku audio, a następnie mieć spójny / wiecznie zielony sposób na jego odtworzenie. Z drugiej strony elementy odtwarzacza audio mogą łatwo ulec zmianie wraz ze zmianą technologii i platform lub dodaniem nowych opcji. Wszystkie te szczegóły powinny znajdować się pod interfejsem większego obiektu złożonego, IMO, i nie powinno być konieczne przepisywanie kontrolera, gdy zmieniają się szczegóły dotyczące odtwarzania dźwięku. Następnie, gdy przekazujesz instancję obiektu ze szczegółami, takimi jak lokalizacja pliku, do większego obiektu, wszystko to zamiana odbywa się we wnętrzu odpowiedniego kontekstu, w którym ktoś jest mniej skłonny zrobić z nim coś głupiego.

Więc w tym przypadku nie sądzę, że to rzucanie instancją obiektu może być dla ciebie problemem. Chodzi o to, że kapitan Picard biegnie do maszynowni, aby włączyć rdzeń osnowy, biegnie z powrotem do mostu, aby wyznaczyć współrzędne, a następnie naciska przycisk „uderz go” po włączeniu osłon, zamiast po prostu powiedzieć „Weź nas na planetę X w Warp 9. Zrób to. ” i pozwalając swojej załodze uporządkować szczegóły. Ponieważ, gdy sobie z tym poradzi, może kapitanem każdego statku we flocie, nie znając układu każdego statku i tego, jak wszystko działa. I to ostatecznie największa wygrana w projektowaniu OOP, do której strzelać, IMO.

Erik Reppen
źródło
2

To dość powszechny projekt, który się dzieje, chociaż możesz mieć problemy z opóźnieniami, jeśli twoja aplikacja jest wrażliwa na tego typu rzeczy.

James
źródło
2

Ten problem można rozwiązać za pomocą dynamicznie zmienianych zmiennych, jeśli ma je Twój język, lub lokalnej pamięci wątków. Mechanizmy te pozwalają nam powiązać niektóre zmienne niestandardowe z łańcuchem aktywacyjnym lub wątkiem kontroli, abyśmy nie musieli przekazywać tych wartości do kodu, który nie ma z nimi nic wspólnego, aby można je było przekazać do innego kodu który ich potrzebuje.

Kaz
źródło
2

Jak wskazały inne odpowiedzi, nie jest to z natury zły projekt. Może tworzyć ścisłe sprzężenie między klasami zagnieżdżonymi i tymi, które je zagnieżdżają, ale rozluźnienie sprzężenia może nie być prawidłową opcją, jeśli zagnieżdżenie odniesień zapewnia wartość dla projektu.

Jednym z możliwych rozwiązań jest „spłaszczenie” zagnieżdżonych odniesień w klasie kontrolera.

Zamiast kilkakrotnie przekazywać parametr przez zagnieżdżone obiekty, można zachować w klasie kontrolera odwołania do wszystkich zagnieżdżonych obiektów.

To, jak dokładnie zostanie to zaimplementowane (lub nawet jeśli jest to prawidłowe rozwiązanie), zależy od aktualnego projektu systemu, takiego jak:

  • Czy jesteś w stanie utrzymywać jakąś mapę zagnieżdżonych obiektów w kontrolerze bez nadmiernego komplikowania?
  • Kiedy przekazujesz parametr do odpowiedniego zagnieżdżonego obiektu, czy zagnieżdżony obiekt może natychmiast rozpoznać parametr, czy też występowała dodatkowa funkcjonalność podczas przekazywania go przez zagnieżdżone obiekty?
  • itp.

Jest to problem, który napotkałem we wzorcu projektowym MVC dla klienta GXT. Nasze komponenty GUI zawierały zagnieżdżone komponenty GUI dla kilku warstw. Kiedy dane modelu zostały zaktualizowane, ostatecznie przerzuciliśmy je przez kilka warstw, aż dotarły do ​​odpowiednich komponentów. Stworzyło to niepożądane połączenie między komponentami GUI, ponieważ jeśli chcieliśmy, aby nowa klasa komponentów GUI akceptowała dane modelu, musieliśmy stworzyć metody aktualizacji danych modelu we wszystkich komponentach GUI, które zawierały nową klasę.

Aby to naprawić, utrzymaliśmy w klasie View mapę odniesień do wszystkich zagnieżdżonych komponentów GUI, aby za każdym razem, gdy dane modelu były aktualizowane, widok mógł wysyłać zaktualizowane dane modelu bezpośrednio do komponentów GUI, które tego potrzebowały, koniec historii . Działa to dobrze, ponieważ były tylko pojedyncze wystąpienia każdego komponentu GUI. Widziałem, że nie działa tak dobrze, jeśli wystąpiło wiele wystąpień niektórych składników GUI, co utrudnia określenie, która kopia wymaga aktualizacji.

David Kaczyński
źródło
0

To, co opisujesz, to tak zwany wzorzec projektowania łańcucha odpowiedzialności . Apple wykorzystuje ten wzorzec do obsługi swojego systemu zdarzeń i do tego, ile jest wart.

użytkownik8865
źródło