Istnieje wiele powodów, dla których globale są złe w OOP.
Jeśli liczba lub rozmiar obiektów wymagających współdzielenia jest zbyt duża, aby można je było efektywnie przekazać w parametrach funkcji, zwykle wszyscy zalecają Wstrzykiwanie zależności zamiast obiektu globalnego.
Jednak w przypadku, gdy prawie wszyscy muszą wiedzieć o pewnej strukturze danych, dlaczego wstrzykiwanie zależności jest lepsze niż obiekt globalny?
Przykład (uproszczony, aby ogólnie pokazać punkt, bez sięgania zbyt głęboko w konkretną aplikację)
Istnieje wiele wirtualnych pojazdów, które mają ogromną liczbę właściwości i stanów, od typu, nazwy, koloru, prędkości, położenia itp. Wielu użytkowników może nimi sterować zdalnie i ogromną liczbę zdarzeń (zarówno inicjowane i automatyczne) mogą zmieniać wiele swoich stanów lub właściwości.
Naiwnym rozwiązaniem byłoby po prostu zrobienie z nich globalnego pojemnika
vector<Vehicle> vehicles;
do którego można uzyskać dostęp z dowolnego miejsca.
Bardziej przyjaznym dla OOP rozwiązaniem byłoby posiadanie kontenera należącego do klasy, która obsługuje główną pętlę zdarzeń i tworzenie instancji w jej konstruktorze. Każda klasa, która tego potrzebuje i jest członkiem głównego wątku, otrzyma dostęp do kontenera za pomocą wskaźnika w swoim konstruktorze. Na przykład, jeśli wiadomość zewnętrzna przychodzi przez połączenie sieciowe, klasa (jedna dla każdego połączenia) obsługująca parsowanie przejmie kontrolę, a parser będzie miał dostęp do kontenera za pomocą wskaźnika lub odwołania. Teraz, jeśli przeanalizowany komunikat powoduje albo zmianę elementu kontenera, albo wymaga pewnych danych z niego, aby wykonać akcję, można go obsłużyć bez potrzeby rzucania około tysięcy zmiennych przez sygnały i szczeliny (lub, co gorsza, przechowywanie ich w parserze do późniejszego pobrania przez osobę, która wywołała parser). Oczywiście wszystkie klasy, które uzyskują dostęp do kontenera poprzez wstrzyknięcie zależności, są częścią tego samego wątku. Różne wątki nie będą miały do niego bezpośredniego dostępu, ale wykonają swoją pracę, a następnie wyślą sygnały do głównego wątku, a gniazda w głównym wątku zaktualizują kontener.
Jeśli jednak większość klas uzyska dostęp do kontenera, co tak naprawdę różni się od globalnego? Jeśli tak wiele klas potrzebuje danych w kontenerze, to czy „sposób wstrzykiwania zależności” nie jest tylko ukrytym globalnym?
Jedną odpowiedzią byłoby bezpieczeństwo wątków: chociaż staram się nie nadużywać globalnego kontenera, być może inny programista w przyszłości, pod presją bliskiego terminu, mimo wszystko użyje globalnego kontenera w innym wątku, nie dbając o wszystko przypadki kolizji. Jednak nawet w przypadku wstrzykiwania zależności można wskazać wskaźnik komuś, kto działa w innym wątku, co prowadzi do tych samych problemów.
Odpowiedzi:
Zastrzyk zależności jest najlepszą rzeczą od krojonego chleba , podczas gdy globalne obiekty znane są od dziesięcioleci jako źródło wszelkiego zła , więc jest to dość interesujące pytanie.
Celem zastrzyku zależności nie jest po prostu zapewnienie, że każdy aktor, który potrzebuje jakiegoś zasobu, może go mieć, ponieważ oczywiście, jeśli sprawisz, że wszystkie zasoby będą globalne, to każdy aktor będzie miał dostęp do każdego zasobu rozwiązanego problemu, prawda?
Punkt wstrzyknięcia zależności to:
Fakt, że w konkretnej konfiguracji wszyscy aktorzy potrzebują dostępu do tej samej instancji zasobu, nie ma znaczenia. Zaufaj mi, pewnego dnia będziesz musiał zmienić konfigurację, aby aktorzy mieli dostęp do różnych instancji zasobu, a potem zdasz sobie sprawę, że zamalowałeś się w kącie. Niektóre odpowiedzi wskazywały już na taką konfigurację: testowanie .
Kolejny przykład: załóżmy, że podzieliłeś aplikację na klient-serwer. Wszyscy aktorzy na kliencie używają tego samego zestawu zasobów centralnych na kliencie, a wszyscy aktorzy na serwerze używają tego samego zestawu zasobów centralnych na serwerze. Załóżmy, że pewnego dnia zdecydujesz się utworzyć „autonomiczną” wersję aplikacji klient-serwer, w której zarówno klient, jak i serwer są spakowane w jednym pliku wykonywalnym i uruchomione na tej samej maszynie wirtualnej. (Lub środowisko wykonawcze, w zależności od wybranego języka).
Jeśli używasz wstrzykiwania zależności, możesz łatwo upewnić się, że wszyscy aktorzy klienta otrzymują instancje zasobów klienta do pracy, podczas gdy wszyscy aktorzy serwera otrzymują instancje zasobów serwera.
Jeśli nie używasz wstrzykiwania zależności, nie masz szczęścia, ponieważ tylko jedna globalna instancja każdego zasobu może istnieć na jednej maszynie wirtualnej.
Następnie należy rozważyć: czy wszyscy aktorzy naprawdę potrzebują dostępu do tego zasobu? naprawdę?
Możliwe, że popełniłeś błąd, przekształcając ten zasób w obiekt bogów (więc oczywiście każdy potrzebuje do niego dostępu), a może rażąco przeceniasz liczbę aktorów w twoim projekcie, którzy faktycznie potrzebują dostępu do tego zasobu .
Dzięki globals każdy wiersz kodu źródłowego w całej aplikacji ma dostęp do każdego zasobu globalnego. Dzięki wstrzykiwaniu zależności każda instancja zasobu jest widoczna tylko dla aktorów, którzy jej faktycznie potrzebują. Jeśli oba są takie same (aktorzy, którzy potrzebują określonego zasobu, stanowią 100% wierszy kodu źródłowego w twoim projekcie), to musiałeś popełnić błąd w swoim projekcie. Więc też
Przekształć ten wielki wielki bóg w mniejsze pod-zasoby, aby różni aktorzy potrzebowali dostępu do różnych jego fragmentów, ale rzadko aktor potrzebuje wszystkich swoich fragmentów lub
Przeanalizuj aktorów, aby z kolei zaakceptowali jako parametry tylko podzbiory problemu, nad którymi muszą pracować, aby nie musieli przez cały czas konsultować się z jakimś wielkim, wielkim, centralnym zasobem.
źródło
To wątpliwe twierdzenie. Link, którego użyjesz jako dowodu, odnosi się do globalnych zmiennych - stanowych . Są zdecydowanie źli. Globale tylko do odczytu są tylko stałymi. Stałe są względnie zdrowe. To znaczy, nie weźmiesz wartości pi do wszystkich swoich klas, prawda?
Nie, nie robią tego.
Jeśli twoje funkcje / klasy mają zbyt wiele zależności, ludzie zalecają zatrzymanie się i sprawdzenie, dlaczego twój projekt jest taki zły. Mnóstwo zależności jest znakiem, że twoja klasa / funkcja prawdopodobnie robi zbyt wiele rzeczy i / lub nie masz wystarczającej abstrakcji.
To koszmar projektowania. Masz wiele rzeczy, których można używać / nadużywać bez żadnego sposobu sprawdzania poprawności, bez ograniczeń współbieżności - to po prostu połączenie.
Tak, łączenie się ze wspólnymi danymi wszędzie nie jest zbyt różne od globalnego i jako takie jest nadal złe.
Plusem jest to, że oddzielasz konsumentów od samej zmiennej globalnej. Zapewnia to pewną elastyczność w tworzeniu instancji. To również nie zmusza cię do posiadania tylko jednej instancji. Możesz stworzyć różne, które zostaną przekazane różnym konsumentom. W razie potrzeby możesz także utworzyć różne implementacje interfejsu dla każdego konsumenta.
A ze względu na zmiany, które nieuchronnie wiążą się ze zmieniającymi się wymaganiami oprogramowania, taki podstawowy poziom elastyczności jest niezbędny .
źródło
foo
s”?Są trzy główne powody, które powinieneś rozważyć.
Podsumowując: DI nie jest celem, jest tylko narzędziem, które umożliwia osiągnięcie celu. Jeśli użyjesz go niewłaściwie (tak jak w twoim przykładzie), nie zbliżysz się do celu, nie ma magicznej właściwości DI, która uczyniłaby zły projekt dobrym.
źródło
Tak naprawdę chodzi o testowalność - zwykle obiekt globalny jest bardzo łatwy w utrzymaniu i łatwy do odczytania / zrozumienia (oczywiście gdy już się zorientujesz), ale jest to stałe narzędzie, a kiedy przychodzisz podzielić swoje klasy na pojedyncze testy, okazuje się, że twój globalny obiekt utknął mówiąc „co ze mną”, a zatem twoje izolowane testy wymagają włączenia globalnego obiektu w nie. Nie pomaga to, że większość platform testowych nie obsługuje łatwo tego scenariusza.
Więc tak, wciąż jest globalny. Podstawowy powód, dla którego nie został zmieniony, tylko sposób, w jaki jest dostępny.
Jeśli chodzi o inicjalizację, jest to nadal problem - wciąż musisz ustawić konfigurację, aby testy mogły zostać uruchomione, aby nic tam nie zyskać. Przypuszczam, że możesz podzielić duży obiekt zależny na wiele mniejszych i przekazać odpowiedni do klas, które ich potrzebują, ale prawdopodobnie masz jeszcze większą złożoność, aby przeważyć wszelkie korzyści.
Niezależnie od tego, jest to przykład „machania ogonem psa”, potrzeba przetestowania decyduje o tym, jak zbudowana jest baza kodu (podobne problemy pojawiają się w przypadku elementów takich jak klasy statyczne, np. Bieżąca data / godzina).
Osobiście wolę umieścić wszystkie moje dane globalne w obiekcie globalnym (lub kilku), ale zapewniam akcesoria, aby uzyskać bity, których potrzebują moje klasy. Następnie mogę wyśmiewać te, aby zwrócić dane, które chcę, jeśli chodzi o testowanie bez dodatkowej złożoności DI.
źródło
Oprócz odpowiedzi, które już masz,
Jedną z cech charakterystycznych programowania OOP jest zachowanie właściwości, których naprawdę potrzebujesz dla określonego obiektu,
TERAZ JEŻELI Twój obiekt ma zbyt wiele właściwości - powinieneś podzielić go dalej na podobiekty.
Edytować
Wspomniałem już, dlaczego obiekty globalne są złe, dodałbym do tego jeden przykład.
Musisz przeprowadzić testowanie jednostkowe kodu GUI formularza systemu Windows, jeśli używasz globalnego, musisz utworzyć wszystkie przyciski, a na przykład zdarzenia, aby utworzyć odpowiedni przypadek testowy ...
źródło