Wszyscy wiemy, jak złe są Singletony, ponieważ ukrywają zależności i z innych powodów .
Jednak we frameworku może istnieć wiele obiektów, które trzeba utworzyć tylko raz i wywołać z dowolnego miejsca (rejestrator, baza danych itp.).
Aby rozwiązać ten problem, powiedziano mi, żebym użył tak zwanego „Menedżera obiektów” (lub kontenera usług, takiego jak symfony), który wewnętrznie przechowuje każde odniesienie do usług (rejestrator itp.).
Ale dlaczego usługodawca nie jest tak zły jak czysty singleton?
Dostawca usług również ukrywa zależności i po prostu kończy tworzenie pierwszej instancji. Tak więc naprawdę staram się zrozumieć, dlaczego powinniśmy używać usługodawcy zamiast singletonów.
PS. Wiem, że aby nie ukrywać zależności powinienem używać DI (jak stwierdził Misko)
Dodaj
Dodałbym: W dzisiejszych czasach singletony nie są takie złe, twórca PHPUnit wyjaśnił to tutaj:
DI + Singleton rozwiązuje problem:
<?php
class Client {
public function doSomething(Singleton $singleton = NULL){
if ($singleton === NULL) {
$singleton = Singleton::getInstance();
}
// ...
}
}
?>
to całkiem sprytne, nawet jeśli nie rozwiązuje wszystkich problemów.
Czy poza DI i Service Container istnieją dobre akceptowalne rozwiązania umożliwiające dostęp do tych obiektów pomocniczych?
źródło
Odpowiedzi:
Service Locator to po prostu mniejsze zło, że tak powiem. „Mniejszy” sprowadzający się do tych czterech różnic ( przynajmniej nie mogę teraz wymyślić żadnych innych ):
Zasada pojedynczej odpowiedzialności
Service Container nie narusza zasady pojedynczej odpowiedzialności, tak jak robi to Singleton. Pojedyncze elementy łączą tworzenie obiektów i logikę biznesową, podczas gdy kontener usług jest ściśle odpowiedzialny za zarządzanie cyklami życia obiektów aplikacji. Pod tym względem Service Container jest lepszy.
Sprzęganie
Singletony są zwykle zakodowane na stałe w aplikacji ze względu na statyczne wywołania metod, co prowadzi do ścisłych powiązań i trudnych do makietowania zależności w kodzie. Z drugiej strony SL to tylko jedna klasa i można ją wstrzykiwać. Więc chociaż wszystkie twoje klasyfikacje będą od tego zależeć, przynajmniej jest to zależność luźno związana. Więc jeśli nie zaimplementowałeś ServiceLocator jako samego Singletona, jest to nieco lepsze i łatwiejsze do przetestowania.
Jednak wszystkie klasy używające ServiceLocator będą teraz zależały od ServiceLocator, który jest również formą sprzężenia. Można to złagodzić, używając interfejsu dla ServiceLocator, więc nie jesteś związany z konkretną implementacją ServiceLocator, ale twoje klasy będą zależeć od istnienia jakiegoś rodzaju Locatora, podczas gdy brak ServiceLocator w ogóle zwiększa ponowne użycie.
Ukryte zależności
Problem ukrywania zależności istnieje jednak bardzo mocno. Kiedy po prostu wstrzykniesz lokalizator do klas konsumujących, nie będziesz znać żadnych zależności. Ale w przeciwieństwie do Singletona, SL zwykle tworzy instancję wszystkich zależności potrzebnych za kulisami. Więc kiedy pobierasz usługę, nie kończysz jak Misko Hevery w przykładzie z kartą kredytową , np. Nie musisz ręcznie tworzyć instancji wszystkich zależności zależności.
Pobieranie zależności z wnętrza instancji również narusza prawo Demeter , które stanowi, że nie należy kopać w współpracowników. Instancja powinna rozmawiać tylko ze swoimi bezpośrednimi współpracownikami. Jest to problem związany z usługami Singleton i ServiceLocator.
Stan globalny
Problem ze stanem globalnym jest również nieco złagodzony, ponieważ podczas tworzenia instancji nowego lokalizatora usług między testami wszystkie wcześniej utworzone instancje są również usuwane (chyba że popełniłeś błąd i zapisałeś je w statycznych atrybutach w SL). Oczywiście nie dotyczy to żadnego stanu globalnego w klasach zarządzanych przez SL.
Zobacz także Fowler on Service Locator vs Dependency Injection, aby uzyskać znacznie bardziej dogłębną dyskusję.
Notatka na temat Twojej aktualizacji i link do artykułu Sebastiana Bergmanna na temat testowania kodu używającego Singletonów : Sebastian w żaden sposób nie sugeruje, że proponowane obejście sprawia, że używanie Singleonów jest mniejszym problemem. To tylko jeden ze sposobów na stworzenie bardziej testowalnego kodu, którego w innym przypadku nie byłoby możliwe do przetestowania. Ale nadal jest to problematyczny kod. W rzeczywistości wyraźnie zauważa: „Tylko dlatego, że możesz, nie oznacza, że powinieneś”.
źródło
Wzorzec lokalizatora usług jest anty-wzorcem. Nie rozwiązuje problemu ujawniania zależności (nie można powiedzieć, patrząc na definicję klasy, jakie są jej zależności, ponieważ nie są one wstrzykiwane, a zamiast tego są wyciągane z lokalizatora usług).
Twoje pytanie brzmi: dlaczego lokalizatory usług są dobre? Moja odpowiedź brzmi: nie są.
Unikaj, unikaj, unikaj.
źródło
Kontener usług ukrywa zależności tak, jak robi to wzorzec Singleton. Zamiast tego możesz zasugerować użycie kontenerów iniekcji zależności, ponieważ ma on wszystkie zalety kontenera usług, ale nie ma (o ile wiem) wad, które ma kontener usług.
O ile rozumiem, jedyną różnicą między nimi jest to, że w kontenerze usługowym kontenerem usługi jest wstrzykiwany obiekt (ukrywając w ten sposób zależności), kiedy używasz DIC, DIC wstrzykuje odpowiednie zależności za Ciebie. Klasa zarządzana przez DIC jest całkowicie nieświadoma faktu, że jest zarządzana przez DIC, dzięki czemu masz mniej sprzężeń, wyraźne zależności i szczęśliwe testy jednostkowe.
To dobre pytanie w SO, wyjaśniające różnicę obu: Jaka jest różnica między wzorcami Dependency Injection i Service Locator?
źródło
Ponieważ można łatwo zastąpić obiekty w Service Container przez
1) dziedziczenie (dziedziczenie klasy Object Manager i nadpisanie metod)
2) zmiana konfiguracji (w przypadku Symfony)
A singletony są złe nie tylko z powodu wysokiego sprzężenia, ale dlatego, że są _ pojedynczymi _tonami. To zła architektura dla prawie wszystkich rodzajów obiektów.
Za „czysty” DI (w konstruktorach) zapłacisz bardzo dużą cenę - wszystkie obiekty powinny zostać utworzone przed przekazaniem ich do konstruktora. Będzie to oznaczać więcej używanej pamięci i mniejszą wydajność. Poza tym nie zawsze obiekt można po prostu utworzyć i przekazać w konstruktorze - można stworzyć łańcuch zależności ... Mój angielski nie jest na tyle dobry, aby o tym w pełni dyskutować, przeczytaj o tym w dokumentacji Symfony.
źródło
Dla mnie staram się unikać stałych globalnych, singletonów z prostego powodu, są przypadki, w których może być konieczne uruchomienie API.
Na przykład mam front-end i admin. Wewnątrz administratora chcę, aby mogli zalogować się jako użytkownik. Rozważ kod wewnątrz admin.
$frontend = new Frontend(); $frontend->auth->login($_GET['user']); $frontend->redirect('/');
Może to nawiązać nowe połączenie z bazą danych, nowy rejestrator itp. W celu zainicjowania frontendu i sprawdzić, czy użytkownik faktycznie istnieje, czy jest prawidłowy, itp. Używałby również odpowiednich oddzielnych plików cookie i usług lokalizacji.
Mój pomysł na singleton jest taki, że nie możesz dwukrotnie dodać tego samego obiektu wewnątrz rodzica. Na przykład
$logger1=$api->add('Logger'); $logger2=$api->add('Logger');
zostawiłoby ci jedną instancję i wskazujące na nią obie zmienne.
Wreszcie, jeśli chcesz korzystać z programowania obiektowego, pracuj z obiektami, a nie z klasami.
źródło
$api
zmiennej wokół twojego frameworka? Nie rozumiem dokładnie, co masz na myśli. Również jeśli wywołanieadd('Logger')
zwróci tę samą instancję, w zasadzie masz kontener usługi