在我人生的第一次面试中,面试官问我:
可以举几个例子吗?关于Unity中可能用到的设计模式。
我当时对设计模式的学习并没有很深入,因此回答了:对象池应该是享元模式。
但很快遭到了他的否决,我再次提议,应该是享元,但他还是摇了摇头。
毕竟是公司主程,我只能低下头表示,自己会回去看看。
在9个月之后的下午,公司项目中,我被分到的功能,需要使用尘封已久的对象池,我就心血来潮写下来这篇文章
来纠正我当时的错误
关于享元模式与对象池的区别
我比较喜欢举例子,其实两者的区别很大
区别一:对象的使用
比如-127~128的int,如果是享元模式,那当我们去取"0"的时候,就是把原原本本的0给你了;
如果是对象池,那当我们去取"0"的时候,会去池中看看我还有没有多余的"0",有就给你,没就new一个
区别二:对象的状态(内部与外部)
这就没有简单的例子了
比如租房,房子是内部,是不变的,中介是外部的,是你去租房时,传给房子的参数
如果是享元模式,当我们带笨笨中介去房源时,就会得到1000的房租,而狡猾中介是2333
当然内部数据也是能修改的,比如我给房子刷上超级油漆,那带不同外部中介去,就会得到不同房租
而对象池,你带中介去,他说房源有人正在看,给你现场造个房子,全是外部数据的影子
区别三:根本就不是一种存在
区别二里也看得出来,是不可能去现场造房子的
享元模式是一种数据处理用的模式,属于结构型模式,注重外部对象与内部对象之间的衔接。
对象池是一种对象使用方式,即构造型模式,注重对象的使用
相同点
都是为了优化内存而生的
3/15补充
今天又看到了一个好玩的例子
比如我这有一片树林,我准备创建一棵树。
如果是对象池,我会从我的仓库中拿出一颗树,更改它的属性后放在这。如果我的仓库中没有我想要的树先创建一棵树再拿出来。
如果是享元模式,我会找到树林的母树,再照着母树画一颗树。
这时我之后的行为可分为两种:
一种是类似于使用不同颜色的画笔画树,以此得到不同的纸片(改变外部属性),
一种是类似于砍倒这棵母树(影响内部属性或改变行为)。
进行前者时一切正常,只有我操作的物体才会受到影响,
而后者则会让后来者再画树时,得到完全不一样的图案。
对象池源码
对象池单例类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
interface IResetable
{
void OnReset();
}
/*
* 使用方法
* 1. 所有频繁创建回收的物体,用对象池创建回收
* GameObjectPool.Instance.CreatObject("类型", 对象, 位置, 旋转);
* GameObjectPool.Instance.CollectObject(对象);
* 2. 需要创建对象时执行,如果每次创建都需要对对象进行初始化,那就让对象实现IResetable接口。
*/
public class GameObjectPool : MonoSingleton<GameObjectPool>
{
private Dictionary<string, List<GameObject>> cache;
//初始化
public override void Init()
{
base.Init();
cache = new Dictionary<string, List<GameObject>>();
}
/// <summary>
/// 为场景显示物体
/// </summary>
/// <param name="key">物体类别</param>
/// <param name="prefab">物体预制体</param>
/// <param name="pos">创建时位置</param>
/// <param name="rotate">创建时角度</param>
/// <returns></returns>
public GameObject CreatObject(string key, GameObject prefab, Vector3 pos, Quaternion rotate)
{
GameObject go = FindUsableGO(key);
if (go == null) go = AddObject(key, prefab);
//设置物体并return
go.transform.position = pos;
go.transform.rotation = rotate;
go.SetActive(true);
foreach (var item in go.GetComponents<IResetable>())
{
item.OnReset();
}
return go;
}
//找到可用的物体
private GameObject FindUsableGO(string key)
{
if (cache.ContainsKey(key))
return cache[key].Find(g => !g.activeInHierarchy);
return null;
}
//添加物体
private GameObject AddObject(string key, GameObject prefab)
{
GameObject go = Instantiate(prefab);
if (!cache.ContainsKey(key)) cache.Add(key, new List<GameObject>());
cache[key].Add(go);
return go;
}
/// <summary>
/// 回收物体至对象池
/// </summary>
/// <param name="go">物体</param>
/// <param name="delay">延迟时间</param>
public void CollectObject(GameObject go, float delay = 0)
{
go.SetActive(false);
}
/// <summary>
/// 移除该类别
/// </summary>
/// <param name="key">类别</param>
public void Clear(string key)
{ //如果用for,记得从后往前删,list会自动补上
foreach (GameObject item in cache[key])
{
Destroy(item);
}
cache.Remove(key);
}
/// <summary>
/// 移除对象池所有物体
/// </summary>
public void ClearAll()
{
//foreach 只读元素
//遍历 字典 集合
//foreach (KeyValuePair<string, List<GameObject>> item in cache)
//{//异常:无效操作(因为把元素删了,IEnumerable需要用被删的那个元素去MoveNext())
// Clear(item.Key);//删除了字典记录,cache.Remove(key)
//}
//做法就是建一个新的,和删掉的元素不相干
List<string> keys = new List<string>(cache.Keys);//之所以能赋值,是因为他们都有IEnumerable
foreach (string item in keys)
{
Clear(item);//删了字典里的key,列表里的key没动。
}
}
}
标签:享元,对象,GameObject,cache,模式,key,go
From: https://www.cnblogs.com/LateUpdate/p/18076201