前几天跟大家聊了面试时的万向锁解法,
那刻在面试官基因里的问题,还有“Unity设计模式”啦
小星河今天就带大家看一下Unity常见的设计模式~
🟥 单例模式
单例模式是设计模式中很常用的一种模式,它的目的是期望一个类仅有一个实例,
并提供一个访问它的全局访问点。
一个场景不能同时存在多个相同的单例脚本,因为单例脚本的功能就是通过 方法:类.instance.xxx来 访问该脚本,
若有多个相同的脚本,那这个方法就不知道调用哪个单例了。
单例模式有两种写法,一种是每个脚本都写单例的代码
另一种是写好单例代码脚本,其他要实现单例模式的脚本继承它就好了。
首先我们来看第一种:
1️⃣ Unity版本的单例类
Test脚本初始状态:
using UnityEngine; public class Test : MonoBehaviour { }
Test脚本单例模式:
using UnityEngine; public class Test : MonoBehaviour { public static Test Instance; private void Awake() { Instance = this; } }
单例使用方法:
将Test脚本挂载到你想控制的物体上,
现在你就可以在任意脚本中,在Awake生命周期后调用该脚本了
print(Test.Instance.gameObject.name);
2️⃣ 泛型单例模板
上面的方法需要在每个脚本都写代码,积少成多,也有些麻烦
毕竟是能省就省的 高(懒)效(惰)人才,怎么允许这样写呢
而这种方法,仅需一个单例模板类即可,
后续的脚本继承该类,后续脚本便实现了单例模式。
网上的 FindObjectOfType 有一个缺陷,
就是该单例在场景处于关闭状态时,其他方法就没法调用这个单例了。下面的单例模板则解决了这个问题。
using System.Collections.Generic; using System.Linq; using UnityEngine; public class BaseWindow<T> : MonoBehaviour where T : MonoBehaviour { static T instance; public static T Instance { get { if (instance == null) { // 先在场景中找寻 List<T> ts = Skode_GetTObjs<T>(); // 场景中找不到就报错 if (ts.Count == 0) { Debug.Log("该场景找不到该脚本:" + typeof(T)); return instance; } instance = ts[0]; if (ts.Count > 1) { foreach (var VARIABLE in ts) { Debug.Log("场景存在多个" + VARIABLE, VARIABLE.gameObject); } } } return instance; } } /// <summary> /// 获取场景中带有T组件的所有物体 /// </summary> public static List<T> Skode_GetTObjs<T>() where T : MonoBehaviour { List<T> objectsInScene = new List<T>(); //该方法会连带预制体一起查找。因此gameObject.scene.name可过滤掉预制体 foreach (T go in Resources.FindObjectsOfTypeAll<T>().ToList()) { if (!(go.hideFlags == HideFlags.NotEditable || go.hideFlags == HideFlags.HideAndDontSave || go.gameObject.scene.name == null)) objectsInScene.Add(go); } return objectsInScene; } }
使用方法:
1、上方法 BaseWindow 放在Assets中即可。
2、要实现单例的脚本继承 BaseWindow,
public class Test : BaseWindow<Test> { }
3、其他方法便可调用单例脚本啦
print(Test.Instance.gameObject.name);
🟧 观察者模式
定义了对象之间的一对多依赖,
这样一来,当一个对象(被观察者)改变状态时,它的所有依赖(观察者)都会收到通知。
1️⃣观察者脚本
下图的基类很容易理解,方便我们复用,拓展其他组观察者、被观察者;
这儿实现了两个观察者,观察一个被观察者;
程序是在 ObserverMode 的 Start 中启动的。
(最后一个脚本是“一个被观察者”)
现在老弟们可能有疑问:
那观察者是不是在update获取信息,会不会很耗资源呢
不会的。
一会我们测试会发现,当被观察者状态改变时,观察者是只执行了一次代码的。
观察者不主动获取信息。
被观察者状态的改变,是用属性来写的,状态改变只执行一次。
下方脚本的使用方法:
ObserverMode放在场景物体上,其他脚本放在Assets中即可。
2️⃣ 启动类:ObserverMode
using UnityEngine; public class ObserverMode : MonoBehaviour { /// <summary> /// 被观察者 /// </summary> private Subject subject; private void Awake() { //实例化被观察者 subject = new Subject(); //实例化观察者A和B IObserver observerA = new ObserverA(subject); IObserver observerB = new ObserverB(subject); //将观察者A、B添加到观察者状态改变的依赖中去 subject.AddObserber(observerA); subject.AddObserber(observerB); } private void Start() { //改变被观察者的状态,看看A、B两个观察者什么反应 subject.State = "状态A"; } }
3️⃣ 被观察者:Subject
/// <summary> /// 被观察者 /// </summary> public class Subject : ISubject { private string mState; public string State { get { return mState; } set { mState = value; NotifyObserver(); } } }
4️⃣ 被观察者基类:ISubject
using System.Collections.Generic; using UnityEngine; /// <summary> /// 被观察者抽象类 /// </summary> public abstract class ISubject { /// <summary> /// 所有观察这个被观察者的 观察者集合 /// </summary> protected List<IObserver> mObserverList; public ISubject() { mObserverList = new List<IObserver>(); } /// <summary> /// 添加观察者 /// </summary> public void AddObserber(IObserver observer) { if (mObserverList.Contains(observer) == false && observer != null) { mObserverList.Add(observer); return; } Debug.Log("报错"); } /// <summary> /// 移除观察者 /// </summary> public void RemoveObserber(IObserver observer) { if (mObserverList.Contains(observer)) { mObserverList.Remove(observer); return; } Debug.Log("报错"); } /// <summary> /// 通知所有观察者更新 /// </summary> public void NotifyObserver() { foreach (IObserver item in mObserverList) { item.RefreshData(); } } }
5️⃣ 观察者A:ObserverA
using UnityEngine; /// <summary> /// 观察者A /// </summary> public class ObserverA : IObserver { private Subject subject; /// <summary> /// 构造函数,初始化时赋值要观察谁 /// </summary> public ObserverA(Subject value) { subject = value; } public override void RefreshData() { Debug.Log("这儿是A观察者,观察到subjectA状态是:" + subject.State); } }
6️⃣ 观察者B:ObserverB
using UnityEngine; /// <summary> /// 观察者B /// </summary> public class ObserverB : IObserver { private Subject subject; /// <summary> /// 构造函数,初始化时赋值要观察谁 /// </summary> public ObserverB(Subject value) { subject = value; } public override void RefreshData() { Debug.Log("这儿是B观察者,观察到subjectA状态是:" + subject.State); } }
7️⃣ 观察者基类:IObserver
/// <summary> /// 观察者基类 /// </summary> public abstract class IObserver { /// <summary> /// 供被观察者的属性调用。告诉观察者它们观察的数据已改变。 /// 因观察者初始化时便已赋值了要观察的对象,那被告知后,观察者便可使用观察对象的最新数据 /// </summary> public abstract void RefreshData(); }
是不是蛮高效,很有秩序感呢?
🟨 代理模式
1️⃣ 代理模式的定义
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。
这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
是不是很迷惑?
其实就是Delegate
代理模式和观察者模式很像,都是定义了对象之间的一对多依赖,当对象改变状态时,它的所有依赖都会收到通知。
2️⃣ 代理模式和观察者模式的区别
观察者模式观察的是最终的对象,
代理模式观察的是中介。
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。
例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。
3️⃣ 在软件设计中,什么时候用代理模式不用观察者模式呢
比如因为安全原因,不能让别人直接访问一个数据,比如transform.position,因为别人可能干些坏事,去访问transform.gameobject.xxx去了
那我们还需要通知别人该数据已更改,该怎么办呢?
那就要用代理模式了。
4️⃣ 代理模式示例
代理模式之前写过博客,整理的比较全,
代理模式delegate链接:传送门
好啦,今天的分享就到这里了,
小哥哥们,啊是不是表示一下,一键三连扣一波?
我们下节继续分享两种设计模式,面对面试妥妥的。