阅读目录
工厂模式初探
工厂的分类
Singleton Factory
Transient Factory
Pool Factory
小结
一谈到 『IoC』,有经验的程序员马上会联想到控制反转,将创建对象的责任反转给工厂。IoC是依赖注入 『DI』 的核心,大名鼎鼎的Spring框架就是一个非常卓越的的控制反转、依赖注入框架。遗憾的是,我们显然不能在Unity 3D中去使用Spring框架,但思想是相通的——IoC也好,控制反转也罢,本质上是一个工厂,或者又被称为容器,我们可以自己维护一个工厂来实现对对象的管理,这也是本文的核心内容。
工厂模式初探
工厂,顾名思义,就是生产对象的地方。如果之前没有接触过设计模式,你可能会疑惑,我直接使用 『new』 关键字难道不能创建对象吗?为什么还要大费周章的让工厂来创建?当然这是没错的,直接使用 『new』 关键字很简洁,也很易懂,但你考虑过对象的释放吗?你可能会说不用考虑啊,GC会帮我们回收啊。
其实问题就出在这里,因为你没有考虑对象管理的动机,所以就不会有工厂这个概念。试想一下,使用ADO.NET或者JDBC去访问数据库,我们是不是要先建立一个Connection,当工作结束后,Close了这个连接。当再一次需要连接数据库时,再建立一次Connection,这背后其实有隐患。因为和数据库建立连接是非常耗时的,只是我们感受不到。我们能不能在关闭连接时,不销毁对象,而是将其放到一个对象池,当下一次请求来时,直接从对象池中获取。这就是工厂的动机,对对象的创建和释放进行管理,这样可以有效的提高效率。
注:释放指的是对象实现了IDisposable接口的非托管资源,在uMVVM框架,工厂维护的都是托管资源,销毁由GC决定
工厂的分类
在uMVVM框架中,我将工厂分为三类:单例(Singleton),临时(Transient),池(Pool)。
Singleton :该工厂生产的对象是单例的,即一旦生产出来的对象将处理所有的请求,不会因为不同的请求而产生新的对象,通常需要考虑多线程并发问题
Transient :该工厂生产的对象是临时的,转瞬即逝的,即每一次请求产生一个新对象,处理请求完毕后就被销毁
Pool:该工厂并不会无限的创建对象,取而代之的是内部维护了一个对象池,当请求来时,从对象池中获取,当请求处理完毕后,对象也不会被销毁,而是再次放回对象池中
我们可以为这三种工厂声明公共的接口:IObjectFactory,这是非常有必要的,方便在运行时根据需求动态的切换不同工厂:
public interface IObjectFactory { object AcquireObject(string className); object AcquireObject(Type type); object AcquireObject<TInstance>() where TInstance : class, new(); void ReleaseObject(object obj); }
这个接口功能很简单,通过统一的入口对对象进行创建与销毁的管理。
Singleton Factory
有了统一的工厂的接口之后,接下来就是去实现对应的工厂了,第一个要实现的就是 Singleton Factory:
public class SingletonObjectFactory:IObjectFactory { /// <summary> /// 共享的字典,不会因为不同的SingletonObjectFactory对象返回不唯一的实例对象 /// </summary> 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]; } } }
上述代码中,我们需要定义一个全局的字典,用来存储所有的单例,值得注意的是,CachedObjects 字典是一个 static 类型,这表明这是一个共享的字典,不会因为不同的SingletonObjectFactory对象返回不唯一的实例对象。
还有一点,单例模式最好考虑一下多线程并发问题,虽然这是一个 『伪』 需求,毕竟Unity 3D是个单线程应用程序,但 uMVVM 框架还是考虑了多线程并发的问题,使用 lock 关键字,它必须是一个 static 类型,保证 lock 了同一个对象。
Transient Factory
Transient Factory 是最容易实现的工厂,不用考虑多线程并发问题,也不用考虑Pool,对每一次请求返回一个不同的对象:
public class TransientObjectFactory : IObjectFactory { //...省略部分代码... public object AcquireObject<TInstance>() where TInstance : class, new() { var instance = new TInstance(); return instance; } }
Pool Factory
Pool Factory 相对来说是比较复杂的工厂,它对 Transient Factory 进行了升级——创建实例前先去Pool中看看是否有未被使用的对象,有的话,那么直接取出返回,如果没有则向Pool中添加一个。
Pool的实现有两种形式,一种是内置了诸多对象,还有一种是初始时是一个空的池,然后再往里面添加对象。第一种效率更高,直接从池里面拿,而第二种更省内存空间,类似于懒加载,uMVVM 的对象池技术使用第二种模式。
public class PoolObjectFactory : IObjectFactory { /// <summary> /// 封装的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> /// 如果超过了容器大小,是否限制 /// </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> /// 获取对象池中的真正对象 /// </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); } }
上述的代码通过构造函数的 max 决定Pool的大小,limit 参数表示超过Pool容量时,是否可以再继续往Pool中添加数据。方法 GetObject 是最核心的方法,逻辑非常简单,获取对象之前先判断Pool中是否有未被使用的对象,如果有,则返回,如果没有,则根据 limit 参数再决定是否可以往Pool中添加数据。
小结
工厂模式是最常见的设计模式,根据工厂的类型可以获取不同形式的数据对象,比如单例数据、临时数据、亦或是对象池数据。这一章的工厂模式很重要,也是对下一篇对象的注入『Inject』做准备,故称之为理念先行。
源代码托管在Github上,点击此了解