上篇文章介绍了,只需通过实现 IObjectFactory 接口和继承 Pool 类,就可以很方便地实现一个SimpleObjectPool。SimpleObjectPool 可以满足大部分的对象池的需求。而笔者通常将 SimpleObjectPool 用于项目开发,原因是接入比较方便,适合在发现性能瓶颈时迅速接入,不需要更改瓶颈对象的内部代码,而且代码精简较容易掌控。

本篇内容会较多:)

新的需求来了

当我们把对象池应用在框架开发中,我们就有了新的需求。

  • 要保证使用时安全。
  • 易用性。

现在让我们思考下 SimpleObjectPool 哪里不安全?

贴上 SimpleObjectPool 的源码:

        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类型。

public interface IPoolable
{ }

然后我们要给对象池类的泛型加上类型约束,本文的对象池我们叫SafeObjectPool。

public class SafeObjectPool<T> : Pool<T> where T : IPoolable

OK,第一个安全问题解决了。

第二个安全问题来了,我们有可能将一个 IPoolable 对象回收两次。为了解决这个问题,我们可以在SafeObjectPool 维护一个已经分配过的对象容器来记录对象是否被回收过,也可以在 IPoolable 对象中增加是否被回收的标记。这两种方式笔者倾向于后者,维护一个容器的成本相比只是在对象上增加标记的成本来说高太多了。

我们在 IPoolable 接口上增加一个 bool 变量来表示对象是否被回收过。

public interface IPoolAble
{
bool IsRecycled { get; set; }
}

接着在进行 Allocate 和 Recycle 时进行标记和拦截。

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

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 接口增加一个接收重置事件的方法。

public interface IPoolAble
{
void OnRecycled(); bool IsRecycled { get; set; }
}

当 SafeObjectPool 回收对象时来触发它。

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 嘛?

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。

public class DefaultObjectFactory<T> : IObjectFactory<T> where T : new()
{
public T Create()
{
return new T();
}
}

注意下对泛型 T 的约束:) 接下来我们在构造 SafeObjectPool 时,创建一个 DefaultObjectFactory。

public class SafeObjectPool<T> : Pool<T> where T : IPoolAble, new()
{
public SafeObjectPool()
{
mFactory = new DefaultObjectFactory<T>();
}
...

注意 SafeObjectPool 的泛型也要加上 new() 的约束。 这样安全的 SafeObjectPool 已经完成了。 我们先测试下:

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。

输出结果:

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 嘛?是时候该登场了,代码如下所示。

    /// <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 的控制权就要交给池对象来管理。

由于控制权交给池对象管理这个需求不是必须的,所以我们要再提供一个接口

    public interface IPoolType
{
void Recycle2Cache();
}

为什么只有一个 Recycle2Cache,没有 Allocate 相关的方法呢?

因为在池对象创建之前我们没有任何池对象,只能用静态方法创建。这就需要池对象提供一个静态的 Allocate 了。使用方法如下所示。

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
}

贴上测试代码:

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);

测试结果:

OnRecycled
OnRecycled
... x50
msgPool.CurCount:50
msgPool.CurCount:49
OnRecycled
msgPool.CurCount:50
msgPool.CurCount:40

测试结果一致,现在贴上 SafeObejctPool 的全部代码。这篇文章内容好多,写得我都快吐了- -。

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

更多内容

Unity 游戏框架搭建 (二十) 更安全的对象池的更多相关文章

  1. Unity 游戏框架搭建 (二十二) 简易引用计数器

    引用计数是一个很好用的技术概念,不要被这个名字吓到了.首先来讲讲引用计数是干嘛的. 引用计数使用场景 有一间黑色的屋子,里边有一盏灯.当第一个人进屋的时候灯会打开,之后的人进来则不用再次打开了,因为已 ...

  2. Unity 游戏框架搭建 2019 (十八~二十) 概率函数 & GameObject 显示、隐藏简化 & 第二章 小结与快速复习

    在笔者刚做项目的时候,遇到了一个需求.第一个项目是一个跑酷游戏,而跑酷游戏是需要一条一条跑道拼接成的.每个跑道的长度是固定的,而怪物的出现位置也是在跑道上固定好的.那么怪物出现的概率决定一部分关卡的难 ...

  3. Unity 游戏框架搭建 (二) 单例的模板

      上一篇文章中说到的manager of managers,其中每个manager都是单例的实现,当然也可以使用静态类实现,但是相比于静态类的实现,单例的实现更为通用,可以适用大多数情况. 如何设计 ...

  4. Unity 游戏框架搭建 (二十三) 重构小工具 Platform

    在日常开发中,我们经常遇到或者写出这样的代码 var sTrAngeNamingVariable = "a variable"; #if UNITY_IOS || UNITY_AN ...

  5. # Unity 游戏框架搭建 2019 (十六、十七) localPosition 简化与Transform 重置

    在上一篇我们收集了一个 屏幕分辨率检测的一个小工具.今天呢再往下接着探索. 问题 我们今天在接着探索.不管是写 UI 还是写 GamePlay,多多少少都需要操作 Transform. 而在笔者刚接触 ...

  6. Unity 游戏框架搭建 (二十一) 使用对象池时的一些细节

    上篇文章使用SafeObjectPool实现了一个简单的Msg类.代码如下: class Msg : IPoolAble,IPoolType { #region IPoolAble 实现 public ...

  7. Unity 游戏框架搭建 (十) QFramework v0.0.2小结

    从框架搭建系列的第一篇文章开始到现在有四个多月时间了,这段时间对自己来说有很多的收获,好多小伙伴和前辈不管是在评论区还是私下里给出的建议非常有参考性,在此先谢过各位. 说到是一篇小节,先列出框架的概要 ...

  8. Unity 游戏框架搭建 (十三) 无需继承的单例的模板

    之前的文章中介绍的Unity 游戏框架搭建 (二) 单例的模板和Unity 游戏框架搭建 (三) MonoBehaviour单例的模板有一些问题. 存在的问题: 只要继承了单例的模板就无法再继承其他的 ...

  9. Unity 游戏框架搭建 (十六) v0.0.1 架构调整

    背景: 前段时间用Xamarin.OSX开发一些工具,遇到了两个问题. QFramework的大部分的类耦合了Unity的API,这样导致不能在其他CLR平台使用QFramework. QFramew ...

随机推荐

  1. Python cPickle模块

    新博客地址:http://gorthon.sinaapp.com/ 持久性就是指保持对象,甚至在多次执行同一程序之间也保持对象.通过本文,您会对 Python对象的各种持久性机制(从关系数据库到 Py ...

  2. 初学者易上手的SSH-struts2 01环境搭建

    首先,SSH不是一个框架,而是多个框架(struts+spring+hibernate)的集成,是目前较流行的一种Web应用程序开源集成框架,用于构建灵活.易于扩展的多层Web应用程序. 集成SSH框 ...

  3. Vue 爬坑之路(六)—— 使用 Vuex + axios 发送请求

    Vue 原本有一个官方推荐的 ajax 插件 vue-resource,但是自从 Vue 更新到 2.0 之后,官方就不再更新 vue-resource 目前主流的 Vue 项目,都选择 axios ...

  4. javascript-OOP基础详解

      前  言 S     N 今天给大家详解一下面向对象编程(简称OOP)基础,OOP 语言使我们有能力定义自己的对象和变量类型 .对象拥有属性和方法 . 所以今天就给大家详解对象和类 . 1-1简介 ...

  5. php设计模式 工厂模式和单例模式

    一.单例模式//让该类在外界无法造对象//让外界可以造一个对象,做一个静态方法返回对象//在类里面通过让静态变量控制返回对象只能是一个. 单例模式的要点有三个: 一是某个类只能有一个实例: 二是它必须 ...

  6. 在HBulider中如何快速的生成有序(ol)和无序(ul)列表

    首先你需要创建一个HTML文件,然后在body里面写入你要创建的类型(有序或者无序),然后列表的个数个人来定: 格式如下: ul > li * 5    代表我要创建一个列表为5个的无序类型 然 ...

  7. Golang:使用 httprouter 构建 API 服务器

    https://medium.com/@gauravsingharoy/build-your-first-api-server-with-httprouter-in-golang-732b7b01f6 ...

  8. Emgu.CV(三)

    像素交换 private void btn_Exchange_Click(object sender, EventArgs e) { if (imageBox1.Image != null) { va ...

  9. pycharm快捷键(转载)

    1.编辑(Editing) Ctrl + Space 基本的代码完成(类.方法.属性)Ctrl + Alt + Space 快速导入任意类Ctrl + Shift + Enter 语句完成Ctrl + ...

  10. mysql多实例-主从复制安装

    安装环境:Centos6.5 mysql版本:mysql-5.5.32.tar.gz 一:安装前准备: 1.安装一些依赖库 yum install cmake gcc gcc-c++ ncurses- ...