Wiem, że podczas tworzenia aplikacji (natywnych lub internetowych), takich jak te w sklepie Apple AppStore lub Google Play, bardzo często stosuje się architekturę Model-View-Controller.
Czy jednak rozsądne jest również tworzenie aplikacji przy użyciu architektury Component-Entity-System wspólnej w silnikach gier?
design-patterns
architecture
mvc
game-development
applications
Andrew De Andrade
źródło
źródło
Odpowiedzi:
Dla mnie absolutnie. Pracuję w Visual FX i studiowałem różnorodne systemy w tej dziedzinie, ich architektury (w tym CAD / CAM), głodny SDK i wszelkich dokumentów, które dałyby mi wady i zalety pozornie nieskończonych decyzji architektonicznych, które mogą być wykonane, nawet te najbardziej subtelne, nie zawsze wywierają subtelny wpływ.
Efekty wizualne są raczej podobne do gier, ponieważ istnieje jedna centralna koncepcja „sceny” z rzutniami wyświetlającymi renderowane wyniki. Procesy pętli centralnej są często przetwarzane w sposób ciągły w tej scenie w kontekście animacji, gdzie może zachodzić fizyka, emitery cząstek spawnują cząstki, animowane i renderowane siatki, animacje ruchu itp., A ostatecznie je renderują wszystko dla użytkownika na końcu.
Inną podobną koncepcją co najmniej bardzo skomplikowanych silników gier była potrzeba aspektu „projektanta”, w którym projektanci mogliby elastycznie projektować sceny, w tym możliwość samodzielnego programowania (skrypty i węzły).
Przez lata odkryłem, że ECS najlepiej pasuje. Oczywiście to nigdy nie jest całkowicie oddzielone od podmiotowości, ale powiedziałbym, że zdecydowanie wydawało się, że daje najmniej problemów. Rozwiązało to o wiele więcej poważnych problemów, z którymi zawsze się zmagaliśmy, a w zamian dało nam tylko kilka nowych mniejszych.
Tradycyjne OOP
Bardziej tradycyjne podejścia OOP mogą być naprawdę mocne, gdy dobrze orientujesz się w wymaganiach projektowych z góry, ale nie w wymaganiach dotyczących implementacji. Niezależnie od tego, czy jest to bardziej płaskie podejście oparte na wielu interfejsach, czy bardziej zagnieżdżone hierarchiczne podejście ABC, ma on tendencję do utrwalania projektu i utrudnia zmianę, a wdrożenie jest łatwiejsze i bezpieczniejsze. Zawsze występuje potrzeba niestabilności w każdym produkcie, który przechodzi poza jedną wersję, więc podejścia OOP mają tendencję do odchylania stabilności (trudność zmiany i brak powodów zmiany) w kierunku poziomu projektu oraz niestabilności (łatwość zmiany i przyczyny zmiany) do poziomu wdrożenia.
Jednak w obliczu zmieniających się wymagań użytkowników, zarówno projektowanie, jak i implementacja mogą wymagać częstych zmian. Możesz znaleźć coś dziwnego, na przykład silną potrzebę użytkownika analogicznego stworzenia, które musi być jednocześnie rośliną i zwierzęciem, całkowicie unieważniając cały zbudowany przez ciebie model konceptualny. Normalne podejście obiektowe nie chroni cię tutaj i może czasami utrudnić tak nieoczekiwane, przełomowe zmiany. Gdy w grę wchodzą obszary krytyczne pod względem wydajności, powody zmian w projekcie są coraz większe.
Łączenie wielu granularnych interfejsów w celu utworzenia zgodnego interfejsu obiektu może bardzo pomóc w stabilizacji kodu klienta, ale nie pomaga w stabilizacji podtypów, które czasami mogą przewyższyć liczbę zależności klienta. Na przykład jeden interfejs może być używany tylko przez część systemu, ale z tysiącem różnych podtypów implementujących ten interfejs. W takim przypadku utrzymanie złożonych podtypów (złożonych, ponieważ mają tak wiele różnych obowiązków związanych z interfejsem) może stać się koszmarem, a nie kodem używającym ich przez interfejs. OOP ma tendencję do przenoszenia złożoności na poziom obiektu, podczas gdy ECS przenosi go na poziom klienta („systemów”), co może być idealne, gdy jest bardzo mało systemów, ale cała gama zgodnych „obiektów” („jednostek”).
Klasa posiada również swoje dane prywatnie, dzięki czemu może samodzielnie utrzymywać niezmienniki. Niemniej jednak istnieją „zgrubne” niezmienniki, które w rzeczywistości mogą być trudne do utrzymania, gdy obiekty oddziałują na siebie. Aby złożony system jako całość był w stanie prawidłowym, często musi brać pod uwagę złożony wykres obiektów, nawet jeśli ich indywidualne niezmienniki są odpowiednio utrzymywane. Tradycyjne podejścia w stylu OOP mogą pomóc w utrzymaniu ziarnistych niezmienników, ale w rzeczywistości mogą utrudnić utrzymanie szerokich, zgrubnych niezmienników, jeśli obiekty skupią się na malutkich aspektach systemu.
Właśnie tam takie podejścia ECS budujące klocki Lego lub warianty mogą być tak pomocne. Także z uwagi na to, że systemy są bardziej zgrubne w projektowaniu niż zwykły obiekt, łatwiej jest utrzymać tego rodzaju gruboziarniste niezmienniki w widoku z lotu ptaka na system. Wiele interakcji malutkich obiektów zamienia się w jeden duży system skupiający się na jednym szerokim zadaniu zamiast malutkich małych obiektów skupiających się na malutkich zadaniach z wykresem zależności, który obejmowałby kilometr papieru.
Jednak musiałem spojrzeć poza swoją dziedzinę, na branżę gier, aby dowiedzieć się o ECS, chociaż zawsze byłem nastawiony na dane. Co więcej, co zabawne, prawie sam przeszedłem do ECS, iterując i próbując wymyślić lepsze projekty. Nie doszedłem jednak do końca i przeoczyłem bardzo ważny szczegół, którym jest sformalizowanie części „systemowej” i zmiażdżenie komponentów aż do surowych danych.
Spróbuję przejść przez to, jak ostatecznie zdecydowałem się na ECS i jak to rozwiązało wszystkie problemy z poprzednimi iteracjami projektu. Myślę, że to pomoże dokładnie wyjaśnić, dlaczego odpowiedź tutaj może być bardzo silnym „tak”, że ECS ma potencjalnie zastosowanie daleko poza branżą gier.
Architektura Brute Force z lat 80
Pierwsza architektura, nad którą pracowałem w branży VFX, miała długą tradycję, która minęła już dekadę, odkąd dołączyłem do firmy. To było brutalne kodowanie C brutalnej siły (nie nachylenie na C, ponieważ uwielbiam C, ale sposób, w jaki został tutaj użyty, był naprawdę surowy). Miniaturowy i zbyt uproszczony kawałek przypominał takie zależności:
I to jest niezwykle uproszczony schemat jednego maleńkiego elementu systemu. Każdy z tych klientów na diagramie („Rendering”, „Fizyka”, „Ruch”) dostałby jakiś „ogólny” obiekt, przez który sprawdzałby pole typu, na przykład:
Oczywiście ze znacznie brzydszym i bardziej złożonym kodem niż ten. Często z tych skrzynek przełączników wywoływane byłyby dodatkowe funkcje, które rekurencyjnie wykonywałyby przełączanie raz za razem. Ten schemat i kod mogą wyglądać prawie jak ECS-lite, ale nie było wyraźnego rozróżnienia między bytem a komponentem („ czy ten obiekt jest kamerą?”, A nie „czy ten obiekt zapewnia ruch?”) I nie było sformalizowania „systemu” ( tylko kilka zagnieżdżonych funkcji rozrzuconych po całym miejscu i mieszających obowiązki). W takim przypadku prawie wszystko było skomplikowane, każda funkcja mogła potencjalnie doprowadzić do katastrofy.
Nasza procedura testowania tutaj często musiała sprawdzać rzeczy takie jak siatki oddzielone od innych rodzajów przedmiotów, nawet jeśli to samo działo się z oboma, ponieważ brutalna siła kodowania tutaj (często z dużą ilością kopiowania i wklejania) często wykonywana jest bardzo prawdopodobne, że w przeciwnym razie dokładnie ta sama logika może zawieść z jednego typu elementu na drugi. Próba rozszerzenia systemu na nowe typy przedmiotów była dość beznadziejna, mimo że istniała silnie wyrażona potrzeba użytkowników, ponieważ było to zbyt trudne, gdy tak bardzo walczyliśmy o obsługę istniejących rodzajów przedmiotów.
Niektóre zalety:
Niektóre minusy:
Architektura COM z lat 90
Większość branży VFX korzysta z tego stylu architektury z tego, co zebrałem, czytając dokumenty o ich decyzjach projektowych i przeglądając ich zestawy programistyczne.
Może nie być to COM na poziomie ABI (niektóre z tych architektur mogą mieć wtyczki napisane tylko przy użyciu tego samego kompilatora), ale ma wiele podobnych cech z zapytaniami interfejsów wykonywanymi na obiektach, aby zobaczyć, jakie interfejsy obsługują ich komponenty.
Przy takim podejściu
transform
powyższa funkcja analogiczna przypominała tę postać:Jest to podejście, na którym zdecydował się nowy zespół tej starej bazy kodów, aby ostatecznie zrezygnować. To była znacząca poprawa w stosunku do oryginału pod względem elastyczności i łatwości konserwacji, ale wciąż były pewne problemy, które omówię w następnym rozdziale.
Niektóre zalety:
Niektóre minusy:
IMotion
zawsze miałyby dokładnie ten sam stan i dokładnie taką samą implementację dla wszystkich funkcji. Aby temu zaradzić, zaczęlibyśmy centralizować klasy podstawowe i funkcje pomocnicze w całym systemie w celu zapewnienia nadmiarowej implementacji w ten sam sposób dla tego samego interfejsu i być może z wielokrotnym dziedziczeniem za maską, ale było dość bałagan pod maską, mimo że kod klienta miał to łatwe.QueryInterface
funkcję prawie zawsze wyświetlaną jako środkowy do górnego punktu dostępowego, a czasami nawet punkt pierwszy. Aby to złagodzić, robimy takie rzeczy, jak renderowanie części pamięci podręcznej bazy kodów listy obiektów, o których już wiadomo, że obsługująIRenderable
, ale to znacznie zwiększyło złożoność i koszty utrzymania. Podobnie było to trudniejsze do zmierzenia, ale zauważyliśmy pewne spowolnienia w porównaniu do kodowania w stylu C, które robiliśmy wcześniej, gdy każdy interfejs wymagał dynamicznej wysyłki. Rzeczy takie jak nieprzewidywalne oddziały i bariery optymalizacyjne są trudne do zmierzenia poza niewielkim aspektem kodu, ale użytkownicy po prostu zauważają responsywność interfejsu użytkownika i pogarszają się, porównując poprzednie i nowsze wersje oprogramowania obok siebie po stronie obszarów, w których złożoność algorytmiczna się nie zmieniła, tylko stałe.Odpowiedź pragmatyczna: skład
Jedną z rzeczy, które zauważyliśmy wcześniej (a przynajmniej ja byłem), które powodowały problemy, było to, że
IMotion
może być zaimplementowane przez 100 różnych klas, ale z dokładnie tą samą implementacją i powiązanym stanem. Co więcej, byłby używany tylko przez kilka systemów, takich jak rendering, ruch klatek kluczowych i fizyka.W takim przypadku moglibyśmy mieć stosunek 3 do 1 między systemami używającymi interfejsu do interfejsu oraz stosunek 100 do 1 między podtypami implementującymi interfejs do interfejsu.
Złożoność i utrzymanie byłyby wówczas drastycznie wypaczone w stosunku do wdrożenia i utrzymania 100 podtypów, zamiast 3 zależnych systemów klienckich
IMotion
. Spowodowało to przeniesienie wszystkich naszych trudności w utrzymaniu na utrzymanie tych 100 podtypów, a nie 3 miejsca korzystające z interfejsu. Aktualizowanie 3 miejsc w kodzie z kilkoma „pośrednimi łącznikami efektorowymi” (tak jak w zależnościach, ale pośrednio poprzez interfejs, a nie bezpośrednią zależnością), nic wielkiego: aktualizowanie 100 miejsc podtypów z ładunkiem „pośrednich połączeń odprowadzających” , całkiem wielka sprawa *.Musiałem więc mocno naciskać, ale zaproponowałem, abyśmy próbowali stać się bardziej pragmatyczni i zrelaksować cały pomysł „czystego interfejsu”. Nie miało dla mnie sensu tworzenie czegoś
IMotion
całkowicie abstrakcyjnego i bezpaństwowego, chyba że widzimy korzyść z posiadania szerokiej gamy implementacji. W naszym przypadkuIMotion
posiadanie szerokiej gamy wdrożeń zamieniłoby się w koszmar utrzymania, ponieważ nie chcieliśmy różnorodności. Zamiast tego próbowaliśmy stworzyć implementację pojedynczego ruchu, która jest naprawdę dobra w porównaniu ze zmieniającymi się wymaganiami klienta, i często pracowaliśmy nad ideą czystego interfejsu, próbując zmusić każdego implementatoraIMotion
do użycia tej samej implementacji i powiązanego stanu, abyśmy nie „ powielone cele.Interfejsy stały się bardziej podobne do szerokiego
Behaviors
skojarzonego z bytem.IMotion
stałby się po prostuMotion
„komponentem” (zmieniłem sposób, w jaki zdefiniowaliśmy „komponent” z COM, na taki, który jest bliższy zwykłej definicji elementu tworzącego „kompletny” byt).Zamiast tego:
Rozwinęliśmy to do czegoś takiego:
Jest to rażące naruszenie zasady inwersji zależności, aby zacząć przechodzić od abstrakcji z powrotem do konkretów, ale dla mnie taki poziom abstrakcji jest użyteczny tylko wtedy, gdy możemy przewidzieć prawdziwą potrzebę w pewnej przyszłości, ponad uzasadnioną wątpliwość, a nie wykonywanie absurdalnych scenariuszy „co jeśli” całkowicie oderwanych od doświadczenia użytkownika (co prawdopodobnie wymagałoby zmiany projektu), dla takiej elastyczności.
Zaczęliśmy ewoluować do tego projektu.
QueryInterface
stał się bardziej podobnyQueryBehavior
. Co więcej, stosowanie dziedziczenia w tym miejscu wydawało się bezcelowe. Zamiast tego użyliśmy kompozycji. Obiekty zamieniły się w kolekcję komponentów, których dostępność można było sprawdzać i wstrzykiwać w czasie wykonywania.Niektóre zalety:
Motion
implementacji, np. I nie rozproszonej w stu podtypach.Niektóre minusy:
Jednym ze zjawisk, które miały miejsce, było to, że ponieważ straciliśmy abstrakcję tych elementów behawioralnych, mieliśmy ich więcej. Na przykład zamiast
IRenderable
komponentu abstrakcyjnego przymocowalibyśmy obiekt za pomocą betonuMesh
lubPointSprites
komponentu. System renderowania wiedziałby, jak renderowaćMesh
iPointSprites
komponenty, i znajdowałby podmioty, które dostarczają takie komponenty i rysują je. Innym razem mieliśmy różne programy renderująceSceneLabel
, które odkryliśmy, że są potrzebne z perspektywy czasu, dlategoSceneLabel
w takich przypadkach dołączamy do odpowiednich podmiotów (być może oprócz aMesh
). Narzędzie systemu renderującego zostanie następnie zaktualizowane, aby wiedzieć, jak renderować jednostki, które je zapewniły, i to była dość łatwa zmiana.W takim przypadku jednostka składająca się z komponentów mogłaby następnie zostać użyta jako komponent innej jednostki. W ten sposób budowalibyśmy rzeczy, łącząc klocki Lego.
ECS: Systemy i surowe składniki danych
Ten ostatni system był o tyle, o ile sam go stworzyłem, a my wciąż go dręczyliśmy COM. Wydawało się, że chciał zostać systemem składającym się z bytu, ale wtedy nie znałem go. Rozglądałem się za przykładami w stylu COM, które nasyciły moją dziedzinę, kiedy powinienem był szukać silników AAA w poszukiwaniu inspiracji architektonicznych. W końcu zacząłem to robić.
Brakowało mi kilku kluczowych pomysłów:
W końcu opuściłem tę firmę i zacząłem pracować nad ECS jako indyk (wciąż pracuję nad tym, jednocześnie wyczerpując moje oszczędności), i był to zdecydowanie najłatwiejszy system do zarządzania.
Zauważyłem, że podejście ECS rozwiązało problemy, z którymi wciąż borykałem się powyżej. Co najważniejsze, czułem się, jakbyśmy zarządzali „miastami” o zdrowych rozmiarach, a nie malutkimi małymi wioskami o skomplikowanych interakcjach. Nie było tak trudne do utrzymania jak monolityczne „megalopolis”, zbyt duże w populacji, by skutecznie nim zarządzać, ale nie było tak chaotyczne jak świat pełen małych, małych wiosek wchodzących w interakcje ze sobą, gdy tylko myśli się o szlakach handlowych w między nimi powstał koszmarny wykres. ECS rozproszyło całą złożoność w kierunku dużych „systemów”, takich jak system renderowania, „miasto” zdrowej wielkości, ale nie „przeludnione megalopolis”.
Komponenty, które stają się surowymi danymi, na początku wydawały mi się naprawdę dziwne , ponieważ łamią nawet podstawowe zasady ukrywania informacji w OOP. Było to swego rodzaju wyzwanie dla jednej z największych wartości, które ceniłem OOP, a mianowicie jego zdolności do utrzymywania niezmienników, które wymagały enkapsulacji i ukrywania informacji. Zaczęło się jednak nie martwić, ponieważ szybko stało się oczywiste, co dzieje się z kilkanaście tak szerokimi systemami przekształcającymi te dane zamiast rozproszenia takiej logiki w setkach lub tysiącach podtypów wdrażających kombinację interfejsów. Staram się myśleć o tym jak w stylu OOP, z wyjątkiem sytuacji, w których systemy zapewniają funkcjonalność i implementację, która uzyskuje dostęp do danych, komponenty dostarczają dane, a jednostki dostarczają komponenty.
Wbrew intuicji stało się jeszcze łatwiejsze rozumowanie o skutkach ubocznych wywoływanych przez system, gdy tylko garstka nieporęcznych systemów przekształcała dane w szerokie przebiegi. System stał się znacznie bardziej „płaski”, moje stosy wywołań stały się płytsze niż kiedykolwiek wcześniej dla każdego wątku. Mógłbym myśleć o systemie na tym poziomie nadzorcy i nie wpadać w dziwne niespodzianki.
Podobnie, uprościło to nawet obszary krytyczne pod względem wydajności w zakresie eliminacji tych zapytań. Ponieważ idea „Systemu” stała się bardzo sformalizowana, system mógł subskrybować komponenty, którymi był zainteresowany, i po prostu otrzymać buforowaną listę podmiotów spełniających te kryteria. Każda z nich nie musiała zarządzać optymalizacją buforowania, stała się scentralizowana w jednym miejscu.
Niektóre zalety:
Niektóre minusy:
W każdym razie powiedziałbym absolutnie „tak”, a mój osobisty przykład efektów wizualnych jest silnym kandydatem. Ale to wciąż dość podobne do potrzeb gier.
Nie zastosowałem go w bardziej odległych obszarach całkowicie oderwanych od obaw związanych z silnikami gier (efekty wizualne są dość podobne), ale wydaje mi się, że znacznie więcej obszarów jest dobrymi kandydatami na podejście ECS. Być może nawet system GUI byłby odpowiedni dla jednego, ale nadal używam tam bardziej OOP (ale bez głębokiego dziedziczenia w przeciwieństwie do Qt, np.).
Jest to szeroko niezbadane terytorium, ale wydaje mi się odpowiednie, gdy twoje byty mogą się składać z bogatej kombinacji „cech” (i dokładnie tego, jaką kombinację cech dają one podlegać zmianom) i gdzie masz garść uogólnionych systemy przetwarzające jednostki, które mają niezbędne cechy.
W takich przypadkach staje się bardzo praktyczną alternatywą dla każdego scenariusza, w którym możesz ulec pokusie użycia czegoś takiego jak wielokrotne dziedziczenie lub emulacja koncepcji (np. Mixiny) tylko w celu uzyskania setek lub więcej kombinacji w hierarchii głębokiego dziedziczenia lub setek kombinacji klas w płaskiej hierarchii implementujących określoną kombinację interfejsów, ale w których waszych systemów jest niewiele (np. kilkadziesiąt).
W takich przypadkach złożoność bazy kodowej zaczyna być bardziej proporcjonalna do liczby systemów zamiast liczby kombinacji typów, ponieważ każdy typ jest teraz tylko jednostką komponującą komponenty, które są niczym więcej niż surowymi danymi. Systemy GUI w naturalny sposób pasują do tego rodzaju specyfikacji, w których mogą mieć setki możliwych typów widgetów w połączeniu z innymi typami bazowymi lub interfejsami, ale tylko garść systemów do ich przetwarzania (układ układu, system renderowania itp.). Gdyby system GUI korzystał z ECS, prawdopodobnie łatwiej byłoby uzasadnić poprawność systemu, gdy całą funkcjonalność zapewnia garstka tych systemów zamiast setek różnych typów obiektów z odziedziczonymi interfejsami lub klasami podstawowymi. Gdyby system GUI używał ECS, widżety nie miałyby żadnej funkcji, tylko dane. Tylko garstka systemów przetwarzających jednostki widgetów miałaby funkcjonalność. Sposób, w jaki można obsłużyć nadrzędne zdarzenia dla widżetu, jest poza mną, ale na podstawie mojego dotychczasowego ograniczonego doświadczenia nie znalazłem przypadku, w którym tego rodzaju logiki nie można przenieść centralnie do danego systemu w sposób, który z perspektywy czasu otrzymałem o wiele bardziej eleganckie rozwiązanie, jakiego bym się spodziewał.
Chciałbym zobaczyć, jak działa na wielu polach, ponieważ był moim ratownikiem. Oczywiście nie jest to odpowiednie, jeśli twój projekt nie psuje się w ten sposób, od jednostek agregujących komponenty po gruboziarniste systemy przetwarzające te komponenty, ale jeśli naturalnie pasują do tego rodzaju modelu, jest to najwspanialsza rzecz, z jaką się spotkałem .
źródło
Architektura Component-Entity-System dla silników gier działa w grach ze względu na charakter oprogramowania do gier oraz jego unikalne cechy i wymagania jakościowe. Na przykład byty zapewniają jednolite środki adresowania i pracy z rzeczami w grze, które mogą być drastycznie różne pod względem celu i zastosowania, ale muszą być renderowane, aktualizowane lub serializowane / deserializowane przez system w jednolity sposób. Poprzez włączenie modelu komponentu do tej architektury, pozwalasz im zachować prostą strukturę rdzenia, a jednocześnie dodawać więcej funkcji i funkcjonalności w razie potrzeby, z niskim sprzężeniem kodu. Istnieje wiele różnych systemów oprogramowania, które mogłyby skorzystać z właściwości tego projektu, takich jak aplikacje CAD, kodeki A / V,
TL; DR - Wzorce projektowe działają dobrze tylko wtedy, gdy dziedzina problemowa jest wystarczająco dostosowana do funkcji i wad, które narzucają projektowi.
źródło
Jeśli domena problemowa jest do tego dobrze dostosowana, z pewnością.
Moja obecna praca dotyczy aplikacji, która musi obsługiwać różne funkcje w zależności od wielu czynników uruchomieniowych. Korzystanie z encji opartych na komponentach w celu oddzielenia wszystkich tych możliwości i umożliwienia rozszerzenia i testowania w izolacji było dla nas idylliczne.
edycja: Moja praca polega na zapewnieniu łączności z zastrzeżonym sprzętem (w języku C #). W zależności od tego, jaki jest rozmiar urządzenia, jakie oprogramowanie jest na nim zainstalowane, jaki poziom usług zakupił klient itp., Musimy zapewnić różne poziomy funkcjonalności urządzenia. Nawet niektóre funkcje mające ten sam interfejs mają różne implementacje w zależności od wersji urządzenia.
Poprzednie bazy kodu miały bardzo szerokie interfejsy, z których wiele nie zostało zaimplementowanych. Niektóre miały wiele cienkich interfejsów, które następnie zostały statycznie skomponowane w jedną klasę. Niektóre po prostu używały łańcuchów -> słowników łańcuchowych do modelowania. (mamy wiele działów, którzy wszyscy myślą, że mogą to zrobić lepiej)
Wszystkie mają swoje wady. Szerokie interfejsy to półtorej bólu, aby skutecznie kpić / testować. Dodanie nowych funkcji oznacza zmianę interfejsu publicznego (i wszystkich istniejących implementacji). Wiele cienkich interfejsów prowadziło do bardzo brzydkiego kodu, ale odkąd skończyliśmy na przekazywaniu dużego, grubego obiektu testowego wciąż cierpiał. Ponadto cienkie interfejsy nie radziły sobie dobrze ze swoimi zależnościami. Słowniki łańcuchowe mają typowe problemy z analizą i istnieniem, a także dziury w wydajności, czytelności i łatwości konserwacji.
To, czego używamy teraz, to bardzo wąska jednostka, której komponenty zostały odkryte i skomponowane na podstawie informacji o środowisku wykonawczym. Zależności są deklarowane i automatycznie rozwiązywane przez szkielet podstawowego komponentu. Same komponenty mogą być testowane w izolacji, ponieważ działają bezpośrednio z ich zależnościami, a problemy z brakującymi zależnościami są wykrywane wcześnie - w jednym miejscu, a nie przy pierwszym użyciu zależności. Można dodawać nowe (lub testowe) komponenty i nie ma to wpływu na istniejący kod. Konsumenci proszą jednostkę o interfejs do komponentu, więc możemy swobodnie przekręcać różne implementacje (i sposób, w jaki implementacje są mapowane na dane środowiska wykonawczego) ze względną swobodą.
W takiej sytuacji, w której skład obiektu i jego interfejsów może zawierać (bardzo zróżnicowany) podzbiór typowych komponentów, działa on bardzo dobrze.
źródło