How can I properly set up a cache on a typedfactory using the decorator pattern (Castle Windsor)?

227 Views Asked by At

I'm using Castle Windsor and I'm trying to set up a cache on a TypedFactory using the decorator pattern. It works fine until I'm trying to dispose of the Windsor container (when shutting down the application). Basically, my problem is that the TypedFactory is already disposed when I'm trying to dispose of the CachedFactory.

Here is a simplified example of my problem:

using System;
using System.Collections.Generic;
using System.Threading;
using Castle.Facilities.TypedFactory;
using Castle.MicroKernel.Registration;
using Castle.Windsor;

namespace ConsoleApplication1
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            // Setting up the container
            var container = new WindsorContainer();
            container.AddFacility<TypedFactoryFacility>();
            container.Register(
                Component.For<IItemFactory>().ImplementedBy<CachedItemFactory>(), //decorator pattern
                Component.For<IItemFactory>().AsFactory(),
                Component.For<IItem>().ImplementedBy<Item>().LifestyleTransient()
                );

            // Resolving
            var itemFactory = container.Resolve<IItemFactory>();

            // Desired behavior. Works as expected.
            IItem item1 = itemFactory.Create("Item1");
            IItem item2 = itemFactory.Create("Item2");
            IItem anotherItem1 = itemFactory.Create("Item1");
            Console.WriteLine("Item1 == Item2: {0}", item1 == item2); //false
            Console.WriteLine("Item1 == anotherItem1: {0}", item1 == anotherItem1); //true

            // Here is my problem. It throws ObjectDisposedException from _itemFactory in the Dispose function of CachedItemFactory
            container.Dispose();

            Console.WriteLine("End of program");
            Console.ReadKey();
        }
    }

    public interface IItem
    {
        string Name { get; }
    }

    public class Item : IItem
    {
        public Item(string name)
        {
            Name = name;
            Thread.Sleep(1000); //It takes time to create this object
        }

        public string Name { get; private set; }
    }

    public interface IItemFactory
    {
        IItem Create(string name);
        void Release(IItem item);
    }

    public class CachedItemFactory : IItemFactory, IDisposable
    {
        private readonly Dictionary<string, IItem> _cache = new Dictionary<string, IItem>();
        private readonly IItemFactory _itemFactory;

        public CachedItemFactory(IItemFactory itemFactory)
        {
            _itemFactory = itemFactory;
        }

        public IItem Create(string name)
        {
            if (!_cache.ContainsKey(name))
                _cache.Add(name, _itemFactory.Create(name));
            return _cache[name];
        }

        public void Release(IItem item)
        {
        }

        public void Dispose()
        {
            foreach (var item in _cache)
            {
                _itemFactory.Release(item.Value);
            }
            _cache.Clear();
        }
    }
}

Any ideas of what I'm doing wrong? Any conceptual (architectural) errors?

I tried the following with no success:

  1. Registering the TypedFactory before the CachedItemFactory (and marking the CachedItemFactory as IsDefault): not working, same error
  2. Implementing a basic factory by hand instead of using the TypedFactory: same problem

This worked:

  1. Implementing a basic factory by hand, registering it before the CachedItemFactory and marking the CachedItemFactory as IsDefault... but it feels wrong (fragile)...

Any comments?

Thanks a lot!

1

There are 1 best solutions below

0
Marwijn On

You are registering both the cached and typedfactory as singletons (implicitly). If you register the TypeFactory as Transient it will probably (not completely sure) be disposed right after the cached factory.

However since you only try to cleanup the resolved components of the factory you could also simply omit the code in Dispose as the typed factory will release all it's components when it is released.