W zdecydowanej większości przypadków jestem przeciwnikiem singleton’a i uważam to za anty-wzorzec. Dużo lepiej użyć IoC i przekazywać wszędzie w konstruktorach tą samą instancję. Istnieją jednak przypadki, w których użycie singleton’a nie jest brzydkie.
Ostatnio miałem klasę, która potrzebowała pewnych danych – kolekcję prostych struktur. Każda struktura zawiera string i pole bool. Ze względu, że ta kolekcja musi być wykorzystana w kilku klasach, zdecydowałem się na przeniesienie jej do osobnej klasy (singleton’a), która tworzy ją na żądanie.
Załóżmy, że pojedyncza struktura wygląda następująco:
struct ItemInfo { public readonly string Name; public ItemInfo(string name) { Name = name; } }
Następnie w kilku klasach potrzebujemy kolekcji ItemInfo, zawierającej różne nazwy. Singleton nie jest najgorszym rozwiązaniem i mógłby wyglądać następująco:
class DataProvider { private DataProvider() { } private static DataProvider _dataProvider; public static DataProvider Instance { get { return _dataProvider ?? (_dataProvider = new DataProvider()); } } private ItemInfo[] _data; public ItemInfo[] GetData() { if (_data == null) { _data = new[] {new ItemInfo("a"), new ItemInfo("b"), new ItemInfo("c")}; } return _data; } }
Cel został osiągnięty – teraz wszystkie klasy mogą korzystać ze współdzielonych danych. Nie musimy tworzyć kilka razy ItemInfo zawierających te SAME dane.
Niestety, takie dane będą w pamięci przez cały czas życia aplikacji, co nie zawsze ma sens. Przecież możliwe jest, że potrzebujemy ich tylko w danym, specyficznym kontekście. Dobrze byłoby jakoś pozbyć się danych z pamięci, ale jednocześnie nie tracić wspomnianych korzyści oraz nie pisać zbyt wiele kodu.
W tym momencie możemy skorzystać z WeakReference, a mianowicie:
class DataProvider { private DataProvider() { } private static WeakReference<DataProvider> _dataProviderReference; public static DataProvider Instance { get { DataProvider dataProvider; if (_dataProviderReference == null) { dataProvider = new DataProvider(); _dataProviderReference = new WeakReference<DataProvider>(dataProvider); } else if (!_dataProviderReference.TryGetTarget(out dataProvider)) { dataProvider = new DataProvider(); _dataProviderReference.SetTarget(dataProvider); } return dataProvider; } } private ItemInfo[] _data; public ItemInfo[] GetData() { if (_data == null) { _data = new[] {new ItemInfo("a"), new ItemInfo("b"), new ItemInfo("c")}; } return _data; } }
Dzięki takiemu rozwiązaniu, gdy nikt nie korzysta z provider’a, jego instancja po prostu zostanie zwolniona. Trzeba być jednak bardzo uważnym, aby nie skończyło się na tym, że GC zbiera co chwilę zasoby, a my wciąż wywołujemy Instance, tworząc za każdym razem nowy obiekt.
Nazwa Provider może nie jest trafna, ponieważ zwykle implementacja takich klas w formie singleton’a jest złym rozwiązaniem. W moim, konkretnych przykładzie, wszystko było wykonywane w pamięci – proste kontenery zawierające listę różnych współczynników.
Singleton nie jest antywzorcem. To my go źle używamy, pamiętam swoje początkowe nim zafascynowanie kiedy chciałem go używać zawsze i wszędzie. W po okresie fascynacji (około półtorarocznym) prawie nie używam singletonów (w aplikacjach biznesowych nie ma to dużego sensu).
Znalezione na szybko:
http://www.devtrends.co.uk/blog/using-unity's-automatic-factories-to-lazy-load-expensive-dependencies
http://stackoverflow.com/questions/2888621/autofacs-funct-to-resolve-named-service