上篇文章介紹了,只需通過實現 IObjectFactory 接口和繼承 Pool 類,就可以很方便地實現一個SimpleObjectPool。SimpleObjectPool 可以滿足大部分的對象池的需求。而筆者通常將 SimpleObjectPool 用于項目開發,原因是接入比較方便,適合在發現性能瓶頸時迅速接入,不需要更改瓶頸對象的內部代碼,而且代碼精簡較容易掌控。
本篇內容會較多:)
新的需求來了
當我們把對象池應用在框架開發中,我們就有了新的需求。
- 要保證使用時安全。
- 易用性。
現在讓我們思考下 SimpleObjectPool 哪里不安全?
貼上 SimpleObjectPool 的源碼:
```cs
public class SimpleObjectPool<T> : Pool<T>
{
readonly Action<T> mResetMethod;
public SimpleObjectPool(Func<T> factoryMethod, Action<T> resetMethod = null,int initCount = 0)
{
mFactory = new CustomObjectFactory<T>(factoryMethod);
mResetMethod = resetMethod;
for (int i = 0; i < initCount; i++)
{
mCacheStack.Push(mFactory.Create());
}
}
public override bool Recycle(T obj)
{
mResetMethod.InvokeGracefully(obj);
mCacheStack.Push(obj);
return true;
}
}
```
首先不安全的地方是泛型 T,在上篇文章中我們說泛型是靈活的體現,但是在框架設計中未約束的泛型卻有可能是未知的隱患。我們很有可能在寫代碼時把 SimpleObjectPool\<Fish\> 寫成 SimpleObjectPool\<Fit\>,而如果恰好你的工程里有 Fit 類,再加上使用var來聲明變量而不是具體的類型(筆者較喜歡用var),那么這個錯誤要過好久才能發現。
為了解決這個問題,我們要給泛型T加上約束。要求可被對象池管理的對象必須是某種類型。是什么類型呢?就是IPoolAble類型。
```cs
public interface IPoolable
{
}
```
然后我們要給對象池類的泛型加上類型約束,本文的對象池我們叫SafeObjectPool。
```cs
public class SafeObjectPool<T> : Pool<T> where T : IPoolable
```
OK,第一個安全問題解決了。
第二個安全問題來了,我們有可能將一個 IPoolable 對象回收兩次。為了解決這個問題,我們可以在SafeObjectPool 維護一個已經分配過的對象容器來記錄對象是否被回收過,也可以在 IPoolable 對象中增加是否被回收的標記。這兩種方式筆者傾向于后者,維護一個容器的成本相比只是在對象上增加標記的成本來說高太多了。
我們在 IPoolable 接口上增加一個 bool 變量來表示對象是否被回收過。
```cs
public interface IPoolAble
{
bool IsRecycled { get; set; }
}
```
接著在進行 Allocate 和 Recycle 時進行標記和攔截。
```cs
public class SafeObjectPool<T> : Pool<T> where T : IPoolAble
{
...
public override T Allocate()
{
T result = base.Allocate();
result.IsRecycled = false;
return result;
}
public override bool Recycle(T t)
{
if (t == null || t.IsRecycled)
{
return false;
}
t.IsRecycled = true;
mCacheStack.Push(t);
return true;
}
}
```
OK,第二個安全問題解決了。接下來第三個不是安全問題,是職責問題。我們再次觀察下上篇文章中的 SimpleObjectPool
```cs
public class SimpleObjectPool<T> : Pool<T>
{
readonly Action<T> mResetMethod;
public SimpleObjectPool(Func<T> factoryMethod, Action<T> resetMethod = null,int initCount = 0)
{
mFactory = new CustomObjectFactory<T>(factoryMethod);
mResetMethod = resetMethod;
for (int i = 0; i < initCount; i++)
{
mCacheStack.Push(mFactory.Create());
}
}
public override bool Recycle(T obj)
{
mResetMethod.InvokeGracefully(obj);
mCacheStack.Push(obj);
return true;
}
}
```
可以看到,對象回收時的重置操作是由構造函數傳進來的 mResetMethod 來完成的。當然,上篇忘記說了,這也是靈活的體現:)通過將重置的控制權開放給開發者,這樣在接入 SimpleObjectPool 時,不需要更改對象內部的代碼。
在框架設計中我們要收斂一些了,重置的操作要由對象自己來完成,我們要在 IPoolable 接口增加一個接收重置事件的方法。
```cs
public interface IPoolAble
{
void OnRecycled();
bool IsRecycled { get; set; }
}
```
當 SafeObjectPool 回收對象時來觸發它。
```cs
public class SafeObjectPool<T> : Pool<T> where T : IPoolAble
{
...
public override bool Recycle(T t)
{
if (t == null || t.IsRecycled)
{
return false;
}
t.IsRecycled = true;
t.OnRecycled();
mCacheStack.Push(t);
return true;
}
}
```
同樣地,在 SimpleObjectPool 中,創建對象的控制權我們也開放了出去,在 SafeObjectPool 中我們要收回來。還記得上篇文章的 CustomObjectFactory 嘛?
```cs
public class CustomObjectFactory<T> : IObjectFactory<T>
{
public CustomObjectFactory(Func<T> factoryMethod)
{
mFactoryMethod = factoryMethod;
}
protected Func<T> mFactoryMethod;
public T Create()
{
return mFactoryMethod();
}
}
```
CustomObjectFactory 不管要創建對象的構造方法是私有的還是公有的,只要開發者有辦法搞出個對象就可以。現在我們要加上限制,大部分對象是 new 出來的。所以我們要設計一個可以 new 出對象的工廠。我們叫它 DefaultObjectFactory。
```cs
public class DefaultObjectFactory<T> : IObjectFactory<T> where T : new()
{
public T Create()
{
return new T();
}
}
```
注意下對泛型 T 的約束:)
接下來我們在構造 SafeObjectPool 時,創建一個 DefaultObjectFactory。
```cs
public class SafeObjectPool<T> : Pool<T> where T : IPoolAble, new()
{
public SafeObjectPool()
{
mFactory = new DefaultObjectFactory<T>();
}
...
```
注意 SafeObjectPool 的泛型也要加上 new() 的約束。
這樣安全的 SafeObjectPool 已經完成了。
我們先測試下:
```cs
class Msg : IPoolAble
{
public void OnRecycled()
{
Log.I("OnRecycled");
}
public bool IsRecycled { get; set; }
}
private void Start()
{
var msgPool = new SafeObjectPool<Msg>();
msgPool.Init(100,50); // max count:100 init count: 50
Log.I("msgPool.CurCount:{0}", msgPool.CurCount);
var fishOne = msgPool.Allocate();
Log.I("msgPool.CurCount:{0}", msgPool.CurCount);
msgPool.Recycle(fishOne);
Log.I("msgPool.CurCount:{0}", msgPool.CurCount);
for (int i = 0; i < 10; i++)
{
msgPool.Allocate();
}
Log.I("msgPool.CurCount:{0}", msgPool.CurCount);
}
```
由于是框架級的對象池,例子將上文的 Fish 改成 Msg。
輸出結果:
```cs
OnRecycled
OnRecycled
... x50
msgPool.CurCount:50
msgPool.CurCount:49
OnRecycled
msgPool.CurCount:50
msgPool.CurCount:40
```
OK,測試結果沒問題。不過,難道要讓用戶自己去維護 Msg 的對象池?
## 改進:
以上只是保證了機制的安全,這還不夠。
我們想要用戶獲取一個 Msg 對象應該像 new Msg() 一樣自然。要做到這樣,我們需要做一些工作。
首先,Msg 的對象池全局只有一個就夠了,為了實現這個需求,我們會想到用單例,但是 SafeObjectPool 已經繼承了 Pool 了,不能再繼承 QSingleton 了。還記得以前介紹的 QSingletonProperty 嘛?是時候該登場了,代碼如下所示。
``` C#
/// <summary>
/// Object pool.
/// </summary>
public class SafeObjectPool<T> : Pool<T>, ISingleton where T : IPoolAble, new()
{
#region Singleton
protected void OnSingletonInit()
{
}
public SafeObjectPool()
{
mFactory = new DefaultObjectFactory<T>();
}
public static SafeObjectPool<T> Instance
{
get { return QSingletonProperty<SafeObjectPool<T>>.Instance; }
}
public void Dispose()
{
QSingletonProperty<SafeObjectPool<T>>.Dispose();
}
#endregion
```
注意,構造方法的訪問權限改成了 protected.
我們現在不想讓用戶通過 SafeObjectPool 來 Allocate 和 Recycle 池對象了,那么 Allocate 和 Recycle 的控制權就要交給池對象來管理。
由于控制權交給池對象管理這個需求不是必須的,所以我們要再提供一個接口
``` C#
public interface IPoolType
{
void Recycle2Cache();
}
```
為什么只有一個 Recycle2Cache,沒有 Allocate 相關的方法呢?
因為在池對象創建之前我們沒有任何池對象,只能用靜態方法創建。這就需要池對象提供一個靜態的 Allocate 了。使用方法如下所示。
```cs
class Msg : IPoolAble,IPoolType
{
#region IPoolAble 實現
public void OnRecycled()
{
Log.I("OnRecycled");
}
public bool IsRecycled { get; set; }
#endregion
#region IPoolType 實現
public static Msg Allocate()
{
return SafeObjectPool<Msg>.Instance.Allocate();
}
public void Recycle2Cache()
{
SafeObjectPool<Msg>.Instance.Recycle(this);
}
#endregion
}
```
貼上測試代碼:
```cs
SafeObjectPool<Msg>.Instance.Init(100, 50);
Log.I("msgPool.CurCount:{0}", SafeObjectPool<Msg>.Instance.CurCount);
var fishOne = Msg.Allocate();
Log.I("msgPool.CurCount:{0}", SafeObjectPool<Msg>.Instance.CurCount);
fishOne.Recycle2Cache();
Log.I("msgPool.CurCount:{0}", SafeObjectPool<Msg>.Instance.CurCount);
for (int i = 0; i < 10; i++)
{
Msg.Allocate();
}
Log.I("msgPool.CurCount:{0}", SafeObjectPool<Msg>.Instance.CurCount);
```
測試結果:
```cs
OnRecycled
OnRecycled
... x50
msgPool.CurCount:50
msgPool.CurCount:49
OnRecycled
msgPool.CurCount:50
msgPool.CurCount:40
```
測試結果一致,現在貼上 SafeObejctPool 的全部代碼。這篇文章內容好多,寫得我都快吐了- -。
```cs
using System;
/// <summary>
/// I cache type.
/// </summary>
public interface IPoolType
{
void Recycle2Cache();
}
/// <summary>
/// I pool able.
/// </summary>
public interface IPoolAble
{
void OnRecycled();
bool IsRecycled { get; set; }
}
/// <summary>
/// Count observer able.
/// </summary>
public interface ICountObserveAble
{
int CurCount { get; }
}
/// <summary>
/// Object pool.
/// </summary>
public class SafeObjectPool<T> : Pool<T>, ISingleton where T : IPoolAble, new()
{
#region Singleton
public void OnSingletonInit()
{
}
protected SafeObjectPool()
{
mFactory = new DefaultObjectFactory<T>();
}
public static SafeObjectPool<T> Instance
{
get { return QSingletonProperty<SafeObjectPool<T>>.Instance; }
}
public void Dispose()
{
QSingletonProperty<SafeObjectPool<T>>.Dispose();
}
#endregion
/// <summary>
/// Init the specified maxCount and initCount.
/// </summary>
/// <param name="maxCount">Max Cache count.</param>
/// <param name="initCount">Init Cache count.</param>
public void Init(int maxCount, int initCount)
{
if (maxCount > 0)
{
initCount = Math.Min(maxCount, initCount);
mMaxCount = maxCount;
}
if (CurCount < initCount)
{
for (int i = CurCount; i < initCount; ++i)
{
Recycle(mFactory.Create());
}
}
}
/// <summary>
/// Gets or sets the max cache count.
/// </summary>
/// <value>The max cache count.</value>
public int MaxCacheCount
{
get { return mMaxCount; }
set
{
mMaxCount = value;
if (mCacheStack != null)
{
if (mMaxCount > 0)
{
if (mMaxCount < mCacheStack.Count)
{
int removeCount = mMaxCount - mCacheStack.Count;
while (removeCount > 0)
{
mCacheStack.Pop();
--removeCount;
}
}
}
}
}
}
/// <summary>
/// Allocate T instance.
/// </summary>
public override T Allocate()
{
T result = base.Allocate();
result.IsRecycled = false;
return result;
}
/// <summary>
/// Recycle the T instance
/// </summary>
/// <param name="t">T.</param>
public override bool Recycle(T t)
{
if (t == null || t.IsRecycled)
{
return false;
}
if (mMaxCount > 0)
{
if (mCacheStack.Count >= mMaxCount)
{
t.OnRecycled();
return false;
}
}
t.IsRecycled = true;
t.OnRecycled();
mCacheStack.Push(t);
return true;
}
}
```
代碼實現很簡單,但是要考慮很多。
## 總結:
* SimpleObjectPool 適合用于項目開發,漸進式,更靈活。
* SafeObjectPool 適合用于庫級開發,更多限制,要求開發者一開始就想好,更安全。
OK,今天就到這里。
轉載請注明地址:涼鞋的筆記:[liangxiegame.com](http://liangxiegame.com)
## 更多內容
* QFramework 地址:[https://github.com/liangxiegame/QFramework](https://github.com/liangxiegame/QFramework)
* QQ 交流群:[623597263](http://shang.qq.com/wpa/qunwpa?idkey=706b8eef0fff3fe4be9ce27c8702ad7d8cc1bceabe3b7c0430ec9559b3a9ce66)
* **Unity 進階小班**:
* 主要訓練內容:
* 框架搭建訓練(第一年)
* 跟著案例學 Shader(第一年)
* 副業的孵化(第二年、第三年)
* 權益、授課形式等具體詳情請查看[《小班產品手冊》](https://liangxiegame.com/master/intro):https://liangxiegame.com/master/intro
* 關注公眾號:liangxiegame 獲取第一時間更新通知及更多的免費內容。

- 正文
- Unity 游戲框架搭建 2017(一)概述
- Unity 游戲框架搭建 2017(二)單例的模板
- Unity 游戲框架搭建 2017(三)MonoBehaviour 單例的模板
- Unity 游戲框架搭建 2017(四)簡易有限狀態機
- Unity 游戲框架搭建 2017(五)簡易消息機制
- Unity 游戲框架搭建 2017 (六) 關于框架的一些好文和一些思考
- Unity 游戲框架搭建 2017 (七) 減少加班利器-QApp類
- Unity 游戲框架搭建 2017 (八) 減少加班利器-QLog
- Unity 游戲框架搭建 2017 (九) 減少加班利器-QConsole
- Unity 游戲框架搭建 2017 (十) QFramework v0.0.2小結
- Unity 游戲框架搭建 2017 (十一) 簡易 AssetBundle 打包工具 (一)
- Unity 游戲框架搭建 2017 (十二) 簡易 AssetBundle 打包工具 (二)
- Unity 游戲框架搭建 2017 (十三) 無需繼承的單例的模板
- Unity 游戲框架搭建 2017 (十四) 優雅的 QSingleton (零) QuickStart
- Unity 游戲框架搭建 2017 (十四) 優雅的 QSingleton (一) Singleton 單例實現
- Unity 游戲框架搭建 2017 (十四) 優雅的 QSingleton (二) MonoSingleton單例實現
- Unity 游戲框架搭建 2017 (十四) 優雅的 QSignleton (三) 通過屬性器實現 Singleton
- Unity 游戲框架搭建 2017 (十四) 優雅的 QSingleton (四) 屬性器實現 Mono 單例
- Unity 游戲框架搭建 2017 (十四) 優雅的 QSingleton (五) 優雅地進行GameObject命名
- Unity 游戲框架搭建 2017 (十五) 優雅的 QChain (零)
- Unity 游戲框架搭建 2017 (十六) v0.0.3 架構調整
- Unity 游戲框架搭建 2017 (十七) 靜態擴展GameObject 實現鏈式編程
- Unity 游戲框架搭建 2017 (十八) 靜態擴展 + 泛型實現 transform 的鏈式編程
- Unity 游戲框架搭建 2017 (十九) 簡易對象池
- Unity 游戲框架搭建 2017 (二十) 安全的對象池
- Unity 游戲框架搭建 2017 (二十一) 使用對象池時的一些細節
- Unity 游戲框架搭建 2017 (二十二) 簡易引用計數器
- Unity 游戲框架搭建 2017 (二十三) 重構小工具 Platform
- Unity 游戲框架搭建 2017 (二十四) 小結