Singleton oraz WeakReference

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.

2 thoughts on “Singleton oraz WeakReference”

  1. 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).

Leave a Reply

Your email address will not be published.