一谈到 『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上,点击此了解

Unity应用架构设计(7)——IoC工厂理念先行的更多相关文章

  1. Unity 3D Framework Designing(7)——IoC工厂理念先行

    一谈到 『IoC』,有经验的程序员马上会联想到控制反转,将创建对象的责任反转给工厂.IoC是依赖注入 『DI』 的核心,大名鼎鼎的Spring框架就是一个非常卓越的的控制反转.依赖注入框架.遗憾的是, ...

  2. Unity应用架构设计(13)——日志组件的实施

    对于应用程序而言,日志是非常重要的功能,通过日志,我们可以跟踪应用程序的数据状态,记录Crash的日志可以帮助我们分析应用程序崩溃的原因,我们甚至可以通过日志来进行性能的监控.总之,日志的好处很多,特 ...

  3. Unity应用架构设计(11)——一个网络层的构建

    对于客户端应用程序,免不了和远程服务打交道.设计一个良好的『服务层』能帮我们规范和分离业务代码,提高生产效率.服务层最核心的模块一定是怎样发送请求,虽然Mono提供了很多C#网络请求类,诸如WebCl ...

  4. Unity应用架构设计(9)——构建统一的 Repository

    谈到 『Repository』 仓储模式,第一映像就是封装了对数据的访问和持久化.Repository 模式的理念核心是定义了一个规范,即接口『Interface』,在这个规范里面定义了访问以及持久化 ...

  5. Unity应用架构设计(4)——设计可复用的SubView和SubViewModel(Part 1)

    『可复用』这个词相信大家都熟悉,通过『可复用』的组件,可以大大提高软件开发效率. 值得注意的事,当我们设计一个可复用的面向对象组件时,需要保证其独立性,也就是我们熟知的『高内聚,低耦合』原则. 组件化 ...

  6. Unity应用架构设计(1)—— MVVM 模式的设计和实施(Part 2)

    MVVM回顾 经过上一篇文章的介绍,相信你对MVVM的设计思想有所了解.MVVM的核心思想就是解耦,View与ViewModel应该感受不到彼此的存在. View只关心怎样渲染,而ViewModel只 ...

  7. Unity应用架构设计(12)——AOP思想的实践

    想象一下,当程序所有的业务逻辑都完成的时候,你可能还来不及喘口气,紧张的测试即将来临.你的Boss告诉你,虽然程序没问题,但某些方法为什么执行这么慢,性能堪忧.领会了Boss的意图之后,漫长的排查问题 ...

  8. Unity应用架构设计(6)——设计动态数据集合ObservableList

    什么是 『动态数据集合』 ?简而言之,就是当集合添加.删除项目或者重置时,能提供一种通知机制,告诉UI动态更新界面.有经验的程序员脑海里迸出的第一个词就是 ObservableCollection.没 ...

  9. Unity应用架构设计(4)——设计可复用的SubView和SubViewModel(Part 2)

    在我们设计和开发应用程序时,经常要用到控件.比如开发一个客户端WinForm应用程序时,微软就为我们提供了若干控件,这些控件为我们提供了可被定制的属性和事件.属性可以更改它的外观,比如背景色,标题等, ...

随机推荐

  1. ecplise打不开提示Eclipse中...No java virtual machine was found...

    解决办法: 在eclipse.ini文件中最前面添加这两行: -vm C:\Program Files\Java\jdk1.8.0_191\bin\javaw.exe 上面那个路径是你的java jd ...

  2. springboot2.0 redis EnableCaching的配置和使用

    一.前言 关于EnableCaching最简单使用,个人感觉只需提供一个CacheManager的一个实例就好了.springboot为我们提供了cache相关的自动配置.引入cache模块,如下. ...

  3. mybatis查询语句的背后之封装数据

    转载请注明出处... 一.前言 继上一篇mybatis查询语句的背后,这一篇主要围绕着mybatis查询的后期操作,即跟数据库交互的时候.由于本人也是一边学习源码一边记录,内容难免有错误或不足之处,还 ...

  4. C#如何使用REST接口读写数据

    原网站:http://www.codeproject.com/Tips/497123/How-to-make-REST-requests-with-Csharp 一个类,我们拷贝下来直接调用就行: 以 ...

  5. 使用IntelliJ Idea新建SpringBoot项目

    简单给大家介绍一下我来创建SpringBoot项目使用的工具,本人使用IntelliJ Idea来创建项目,利用其中的Spring Initializr工具来快速创建项目. 步骤如下: 菜单栏中选择F ...

  6. PAT (Advanced Level) Practise 1001 解题报告

    GiHub markdown PDF 问题描述 解题思路 代码 提交记录 问题描述 A+B Format (20) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判 ...

  7. DataGrid绑定DataTable出错

    直接用DataGrid.ItemSource = DataTable.DefaultView时会出现以下错误: target element is 'TextBlock' (Name=''); tar ...

  8. HDU.5394.Trie in Tina Town(回文树)

    题目链接 \(Description\) 给定一棵\(Trie\).求\(Trie\)上所有回文串 长度乘以出现次数 的和.这里的回文串只能是从上到下的一条链. 节点数\(n\leq 2\times ...

  9. BZOJ.4888.[TJOI2017]异或和(树状数组)

    BZOJ 洛谷 \(Description\) 求所有区间和的异或和. \(n\leq 10^5,\ \sum a_i\leq 10^6\). \(Solution\) 这样的题还是要先考虑按位做. ...

  10. 1418 抱歉 ACM 欧拉公式

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=1418 思路:一看题目,明显是要求我们找规律,于是我就在草稿纸上画了很多个图像,还是没有找到规律,于是我就在 ...