При описании шаблона Приспособленец для упрощения примера не был рассмотрен механизм удаления неиспользуемых экземпляров. Рассмотрим вариант реализации этой функциональности на C#.
Сразу оговорим, что в качестве основы используем код примера из описания шаблона. Далее будет приведен только новый класс Пула приспособленцев и код для демонстрации его работы.
Кроме того, код будет приводиться по мере его разработки. Поэтому private метод появится до public. В рабочем варианте, разумеется, необходимо отсортировать методы в правильной последовательности.
Основная задача – это определить неиспользуемые экземпляры. Во многих языках программирования для этого пришлось бы ввести подсчет ссылок. Но в C# это можно сделать проще, используя слабые ссылки (WeakReference). Ссылка на экземпляр, хранимая в этом классе, не учитывается при определении мусорных объектов. Поэтому, любой экземпляр Приспособленца, не используемый клиентами, будет уничтожен при очередной сборке мусора.
Перейдем к реализации нового MapComponentFactory. Часть, реализующая шаблон Одиночка, останется не тронутой. Изменения начнутся с замены типа объектов в хранилищах на WeakReference.
public class MapComponentFactory
{
private static readonly Lazy<MapComponentFactory> _instance
= new Lazy<MapComponentFactory>(() => new MapComponentFactory());
public enum Trees { Oak, Spruce, Pine, Birch, Aspen };
public enum Roads { Direct, TurnLeft, TurnRight }
private ConcurrentDictionary<Trees, WeakReference> _trees
= new ConcurrentDictionary<Trees, WeakReference>();
private ConcurrentDictionary<Roads, WeakReference> _roads
= new ConcurrentDictionary<Roads, WeakReference>();
private MapComponentFactory() { }
public static MapComponentFactory Instance
{
get { return MapComponentFactory._instance.Value; }
}
Теперь разработаем метод GetFromPoolOrCreate(), предназначенный для получения объекта из хранилища. Его параметрами будут хранилище (pool), указание требуемого варианта Приспособленца (keyValue) и метод, вызываемый при необходимости порождения экземпляра объекта (valueFactory).
private IMapComponent GetFromPoolOrCreate<TKey>(
ConcurrentDictionary<TKey, WeakReference> pool,
TKey keyValue,
Func<TKey, IMapComponent> valueFactory)
{
IMapComponent component = null;
WeakReference componentRef = pool.GetOrAdd(
keyValue,
(key) => {
component = valueFactory(key);
return new WeakReference(component);
});
component = componentRef.Target as IMapComponent;
if (component == null) {
Console.WriteLine("RECREATE: {0}", keyValue.ToString());
pool.TryRemove(keyValue, out componentRef);
return this.GetFromPoolOrCreate<TKey>(pool, keyValue, valueFactory);
}
return component;
}
Рассмотрим код метода подробнее. Вначале попытаемся получить слабую ссылку на требуемый экземпляр непосредственно из хранилища. Если такой нет, то создадим сам объект и слабую ссылку на него. Все это происходит в вызове GetOrAdd(). При обнаружении уже существующей слабой ссылки проверяем, не был ли сам экземпляр удален сборщиком (componentRef.Target as IMapComponent). Если необходимо – порождаем объект заново. Для этого убираем слабую ссылку из хранилища и вызываем GetFromPoolOrCreate() повторно.
Чтобы в демонстрации можно было заметить повторное создание, добавим вывод сообщения об этом событии на консоль (строка 18).
Остается только написать методы для получения нужных Приспособленцев.
public IMapComponent CreateTree(Trees treeType)
{
return this.GetFromPoolOrCreate<Trees>(
this._trees,
treeType,
key => new MapTreeFlyweight() { Title = key.ToString() });
}
public IMapComponent CreateRoad(Roads roadType)
{
return this.GetFromPoolOrCreate<Roads>(
this._roads,
roadType,
key => new MapRoadFlyweight() { Title = key.ToString() });
}
public IMapComponent CreateHouse(string title)
{
return new MapHouse() { Title = title };
}
Перейдем к демонстрации. В классе Demo перепишем код метода Execute(). В нем создадим карту города и удалим ссылку на нее. После этого вызовем сборку мусора, используя метод GC.Collect().
public static void Execute()
{
IMapComponent city = BuildCity(MapComponentFactory.Instance);
city = null;
GC.Collect();
city = BuildCity(MapComponentFactory.Instance);
}
Если запустить полученный пример, то из сообщений будет видно, что второй вызов BuildCity() приведет к повторному созданию экземпляров в Пуле приспособленцев.