Co jest alternatywą dla Singletona


114

Mamy klasę, która zawiera informacje o konfiguracji aplikacji. Kiedyś to był singleton. Po pewnym przeglądzie architektonicznym kazano nam usunąć singleton. Zauważyliśmy pewne korzyści wynikające z niestosowania singletona w testach jednostkowych, ponieważ możemy testować różne konfiguracje jednocześnie.

Bez singletona musimy przekazać instancję wszędzie w naszym kodzie. Robi się taki bałagan, więc napisaliśmy opakowanie singletona. Teraz przenosimy ten sam kod do PHP i .NET, zastanawiam się, czy istnieje lepszy wzorzec, którego możemy użyć dla obiektu konfiguracyjnego.

Odpowiedzi:


131

Blogu Testowanie Google posiada szereg zapisów o unikaniu Singleton (w celu utworzenia kodu dającej się przetestować). Może to ci pomoże:

W ostatnim artykule szczegółowo wyjaśniono, jak przenieść tworzenie nowych obiektów do fabryki, aby uniknąć używania singletonów. Na pewno warto przeczytać.

Krótko mówiąc, przenosimy wszystkich nowych operatorów do fabryki. Wszystkie obiekty o podobnym okresie życia grupujemy w jedną fabrykę.


3
*** Używanie zastrzyku zależności w celu uniknięcia singletonów
Justin

Te artykuły są tak dobre, jak standardy programowania Google C ++!

2
No nie bardzo. Na przykład rada „Nie używaj metod statycznych” jest sprzeczna z zasadą minimalnego interfejsu Scotta Meyersa / Herb Suttersa. Są przydatne porady, ale brakuje im wkładu wielu umysłów.
Matthieu M.

@FrankS dlaczego zmieniłeś kolejność linków? Na początku było to dobrze chronologiczne.
cregox

@Cawas właściwie nie mam pojęcia, to było ponad cztery lata temu, więc myślę, że miałem wtedy ku temu kilka powodów :-)
FrankS,

15

Najlepszym sposobem jest użycie zamiast tego wzorca Factory. Podczas konstruowania nowej instancji swojej klasy (w fabryce) możesz wstawić dane „globalne” do nowo utworzonego obiektu, jako odniesienie do pojedynczej instancji (którą przechowujesz w klasie fabrycznej) lub kopiując odpowiednie dane do nowego obiektu.

Wszystkie twoje obiekty będą wtedy zawierały dane, które kiedyś żyły w singletonie. Nie sądzę, aby ogólnie różnica była duża, ale może to ułatwić czytanie kodu.


1
Nie zgadzam się ze stwierdzeniem „najlepszy sposób”, ale +1 za dobrą alternatywę.
tylermac

Problem z tym podejściem polega na tym, że każdy nowy obiekt zawiera (lub odniesienia) potencjalnie ogromną porcję danych. var_dump () na którymkolwiek z tych obiektów zawierających gob daje bardzo szybko ogromne listy, swobodnie zasypane ostrzeżeniami o rekursji . To cholernie brzydkie, nie może być strasznie wydajne i sprawia, że ​​wszystko wydaje się szalone. Jednak osobiście nie znalazłem lepszego sposobu. Nagiąłem metodę "factory" do używania __construct () do odniesienia się do global. Jednak wydaje się, że wszystko jest pochylone do tyłu, aby uniknąć przerażającego Singletona ...
FYA

2
@EastGhostCom: równie dobrze możemy użyć singletona i przestać próbować utrudniać sobie życie :)
gbjbaanb

5

Mogę tu stwierdzić oczywiste, ale czy jest powód, dla którego nie można używać frameworka wstrzykiwania zależności, takiego jak Spring lub Guice ? (Wierzę, że Spring jest teraz również dostępny dla .NET).

W ten sposób framework może pomieścić pojedynczą kopię obiektów konfiguracyjnych, a Twoje komponenty bean (usługi, DAO, cokolwiek) nie muszą się martwić o ich wyszukiwanie.

To jest podejście, które zwykle wybieram!


4

Jeśli używasz Spring Framework , możesz po prostu stworzyć zwykły bean. Domyślnie (lub jeśli jawnie ustawisz scope="singleton") tylko jedna instancja komponentu bean jest tworzona i ta instancja jest zwracana za każdym razem, gdy komponent bean jest używany w zależności lub pobierany za pośrednictwem getBean().

Zyskujesz przewagę pojedynczego wystąpienia, bez sprzężenia wzorca Singleton.


3
O ironio - użyj (singleton) wiosennej fasoli, aby zastąpić swój singleton ...
Zack Macomber

4

Alternatywą jest przekazanie tego, czego potrzebujesz, zamiast proszenia obiektu o rzeczy.


4

nie kumuluj odpowiedzialności za pojedynczy obiekt konfiguracyjny, ponieważ kończy się to bardzo dużym obiektem, który jest zarówno trudny do zrozumienia, jak i delikatny.

Na przykład, jeśli potrzebujesz innego parametru do określonej klasy, zmieniasz Configurationobiekt, a następnie ponownie skompiluj wszystkie klasy, które go używają. To jest trochę problematyczne.

Spróbuj refaktoryzować swój kod, aby uniknąć wspólnego, globalnego i dużego Configurationobiektu. Przekaż tylko wymagane parametry do klas klienta:

class Server {

    int port;

    Server(Configuration config) {
        this.port = config.getServerPort();
    } 

}

należy refaktoryzować na:

 class Server {

    public Server(int port) {
       this.port = port;
    }
 }

ramy wtrysk zależność pomogą wiele, ale to nie jest stricly wymagane.


Tak, to naprawdę dobra uwaga. Robiłem to już wcześniej. Mój duży obiekt konfiguracyjny implementował interfejsy, takie jak MailServiceConf, ServerConf .., a następnie framework Dependency Injection przekazuje konfigurację do klas, więc moje klasy nie są zależne od dużego obiektu Configuration.
caltuntas

1

Możesz osiągnąć to samo zachowanie singletona, używając metod statycznych. Steve Yegge wyjaśnia to bardzo dobrze w tym poście.


Właściwie artykuł jest całkiem dobry i nie mówi, że powinieneś zamiast tego używać metod statycznych. Zamiast tego stwierdza, że ​​metody statyczne są również pojedynczymi singletami i na koniec zaleca użycie wzorca metody fabrycznej: „Na koniec stwierdzę, że jeśli nadal czujesz potrzebę używania obiektów Singleton, rozważ zamiast tego użycie wzorca Factory Method. ... ”
FrankS,

0

Czy klasa, która zawiera tylko statyczne metody i pola, jest możliwa? Nie jestem pewien, jaka jest Twoja sytuacja, ale warto się temu przyjrzeć.


1
Jeśli klasa jest bezstanowa, powinna być klasą statyczną.
AlbertoPL

1
Jest w C ++ - wzorzec jest znany jako Monostate.

0

Zależy od tego, jakie narzędzia / ramy itp. Są używane. W przypadku narzędzi do iniekcji zależności / ioc często można nadal uzyskać pojedynczą wydajność / optymalizację, jeśli kontener di / ioc używa zachowania singleton dla wymaganej klasy - (na przykład interfejsu IConfigSettings), tworząc tylko jedną instancję klasy. Można to nadal zastąpić do testowania

Alternatywnie można użyć fabryki, aby utworzyć klasę i zwrócić tę samą instancję za każdym razem, gdy jej zażądasz - ale w celu przetestowania może zwrócić wersję skróconą / fałszywą


0

Przejrzyj możliwość konfiguracji jako interfejsu wywołań zwrotnych. Więc twój kod wrażliwy na konfigurację będzie wyglądał:

MyReuseCode.Configure(IConfiguration)

Kod startowy systemu będzie wyglądał:

Library.init(MyIConfigurationImpl)

0

Można użyć struktury iniekcji zależności, aby złagodzić ból związany z przekazywaniem obiektu konfiguracyjnego. Przyzwoity jest ninject, który ma tę zaletę, że używa kodu zamiast XML.


0

Może też niezbyt czysty, ale może możesz przekazać bity informacyjne, które chcesz zmienić, do metody, która tworzy singleton - zamiast używać

public static Singleton getInstance() {
    if(singleton != null)
        createSingleton();
        return singleton;
    }
}

możesz wywołać createSingleton(Information info)bezpośrednio przy starcie aplikacji (oraz w metodzie setUp testów jednostkowych).


-1

Singletony nie są złe, ale wzór projektu jest wadliwy. Mam klasę, w której chcę utworzyć tylko jedno jej wystąpienie w czasie wykonywania, ale chcę utworzyć wiele izolowanych wystąpień podczas testów jednostkowych, aby zapewnić deterministyczne wyniki.

DI przy użyciu Springa itp. Jest bardzo dobrą opcją, ale nie jedyną.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.