When IoC is first mentioned, veteran developers think of Inversion of Control, or reversal of responsibilities with a Factory. IoC is the “DI” core of Dependency Injection. Spring Framework is the most popular in the .NET world but we cannot use it for Unity3D. But the idea is the same, it’s a IoC factory. It can be called Factory or Container.

Factory Method

Factory, as the name suggests, is where objects are produced. If you haven’t studied Design Pattern before, you might think of creating with the “new” keyword here. However, this takes the lifecycle of the object in memory from us, even if you say that the GC does this for us, this is not enough, the problem arises at this point. Since the objects that take a long time to create and destroy, go through the same process over and over in each use, affecting both efficiency and memory management, here we include these objects in a Pool and request them from this Pool by the Factory.

IoC Factory UML

Factory Classification

In our MVVM project, we divided our Factories into three parts: Singleton, Transient and Pool.

  • Singleton: The object created by the Factory is singular, it is created in the first request and the same object is created in all other requests. Conditions such as race-condition and multi-thread should also be considered while designing.
  • Transient: Objects created by the Factory are temporary and needed for a short time. These types of objects are used in places that need to be used for a process and then destroyed.
  • Pool: Factory instead of creating a single object for incoming requests, updates the previously created objects and makes them available.

First of all, we will define a common interface for these three Factories, “IObjectFactory”, this will help us to change the Factory we work with at runtime according to the needs.

public interface IObjectFactory
{
    object AcquireObject(string className);
    object AcquireObject(Type type);
    object AcquireObject<TInstance>() where TInstance : class, new();
    void ReleaseObject(object obj);
}

Singleton Factory

After creating the Interface, we first create the Singleton Factory with it.

public class SingletonObjectFactory:IObjectFactory
{
    private static Dictionary<Type,object> _cachedObjects = null;
    private static readonly object _lock=new object();
    private Dictionary<Type, object> CachedObjects
    {
        get
        {
            lock (_lock)
            {
                if (_cachedObjects==null)
                {
                    _cachedObjects=new Dictionary<Type, object>();
                }
                return _cachedObjects;
            }
        }
    }

	//.....

    public object AcquireObject<TInstance>() where TInstance:class,new()
    {
        var type = typeof(TInstance);
        if (CachedObjects.ContainsKey(type))
        {
            return CachedObjects[type];
        }
        lock (_lock)
        {
            var instance=new TInstance();
            CachedObjects.Add(type, instance);
            return CachedObjects[type];
        }
    }
}

We made a static Dictionary for common use in all instances of this factory and kept our objects here according to the object type. Also, even though Unity3D is single-thread, we still produced a solution against it in MVVM, here the object that we will lock with lock must also be static!

Transient Factory

Factory is the easiest to implement. There are no Multi-Thread or Pooling issues, you create and return a new object for each request.

public class TransientObjectFactory : IObjectFactory
{
 	//......

    public object AcquireObject<TInstance>() where TInstance : class, new()
    {
        var instance = new TInstance();
        return instance;
    }
}

Pool Factory

Factory is the most difficult implementation compared to others. There are two types of Pool implementations, the first is to create many objects that are ready in the Pool when the Pool is created, and the second is to create and return the objects as the request comes. The second is like Lazy Loading and the second type is used in MVVM technology.

public class PoolObjectFactory : IObjectFactory
{
    /// <summary>
    /// Encapsulated PoolData
    /// </summary>
    private class PoolData
    {
        public bool InUse { get; set; }
        public object Obj { get; set; }
    }

    private readonly List<PoolData> _pool;
    private readonly int _max;
    /// <summary>
    /// If it exceeds the container size, whether to limit
    /// </summary>
    private readonly bool _limit;

    public PoolObjectFactory(int max, bool limit)
    {
        _max = max;
        _limit = limit;
        _pool = new List<PoolData>();
    }

    private PoolData GetPoolData(object obj)
    {
        lock (_pool)
        {
            for (var i = 0; i < _pool.Count; i++)
            {
                var p = _pool[i];
                if (p.Obj == obj)
                {
                    return p;
                }
            }
        }
        return null;
    }
    /// <summary>
    /// Get the real object in the object pool
    /// </summary>
    /// <param name="type"></param>
    /// <returns></returns>
    private object GetObject(Type type)
    {
        lock (_pool)
        {
            if (_pool.Count > 0)
            {
                if (_pool[0].Obj.GetType() != type)
                {
                    throw new Exception(string.Format("the Pool Factory only for Type :{0}", _pool[0].Obj.GetType().Name));
                }
            }

            for (var i = 0; i < _pool.Count; i++)
            {
                var p = _pool[i];
                if (!p.InUse)
                {
                    p.InUse = true;
                    return p.Obj;
                }
            }


            if (_pool.Count >= _max && _limit)
            {
                throw new Exception("max limit is arrived.");
            }

            object obj = Activator.CreateInstance(type, false);
            var p1 = new PoolData
            {
                InUse = true,
                Obj = obj
            };
            _pool.Add(p1);
            return obj;
        }
     }

    private void PutObject(object obj)
    {
        var p = GetPoolData(obj);
        if (p != null)
        {
            p.InUse = false;
        }
    }

    public object AcquireObject(Type type)
    {
        return GetObject(type);
    }

    public void ReleaseObject(object obj)
    {
        if (_pool.Count > _max)
        {
            if (obj is IDisposable)
            {
                ((IDisposable)obj).Dispose();
            }
            var p = GetPoolData(obj);
            lock (_pool)
            {
                _pool.Remove(p);
            }
            return;
        }
        PutObject(obj);
    }
}

As seen above, the pool capacity is determined by the max value given from the parameter. Limit parameter is whether to continue adding objects when the size of the Pool is exceeded. Pool’s bussines logic is under GetObject. If there is an object that is not in use, it returns, otherwise it adds according to the limit parameter.