在我人生的第一次面試中,面試官問我:
可以舉幾個例子嗎?關於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沒動。
}
}
}