Unity精华☀️ 哥哥,你会这么多「设计模式」,面试官会心疼你吧
Posted 橙子SKODE
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity精华☀️ 哥哥,你会这么多「设计模式」,面试官会心疼你吧相关的知识,希望对你有一定的参考价值。
哈喽大家好,你的橙哥突然出现~
本系列博客地址:传送门
前几天跟大家聊了面试时的万向锁解法,
那刻在面试官基因里的问题,还有“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);
二、观察者模式
定义了对象之间的一对多依赖,
这样一来,当一个对象(被观察者)改变状态时,它的所有依赖(观察者)都会收到通知。
我们的脚本:
下图的基类很容易理解,方便我们复用,拓展其他组观察者、被观察者;
这儿实现了两个观察者,观察一个被观察者;
程序是在 ObserverMode 的 Start 中启动的。
(最后一个脚本是“一个被观察者”)
现在老弟们可能有疑问:
那观察者是不是在update获取信息,会不会很耗资源呢
不会的。
一会我们测试会发现,当被观察者状态改变时,观察者是只执行了一次代码的。
观察者不主动获取信息。
被观察者状态的改变,是用属性来写的,状态改变只执行一次。
下方脚本的使用方法:
ObserverMode放在场景物体上,其他脚本放在Assets中即可。
1、启动类: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";
}
}
2、被观察者:Subject
/// <summary>
/// 被观察者
/// </summary>
public class Subject : ISubject
{
private string mState;
public string State
{
get { return mState; }
set
{
mState = value;
NotifyObserver();
}
}
}
3、被观察者基类: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();
}
}
}
4、观察者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);
}
}
5、观察者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);
}
}
6、观察者基类: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链接:传送门
好啦,今天的分享就到这里了,
小哥哥们,啊是不是表示一下,一键三连扣一波?
我们下节继续分享两种设计模式,面对面试妥妥的。
如果你有技术上的问题或困扰
都可以加我的vx(skode250)
和我聊一聊你的故事🧡
以上是关于Unity精华☀️ 哥哥,你会这么多「设计模式」,面试官会心疼你吧的主要内容,如果未能解决你的问题,请参考以下文章
Unity精华☀️ 哥哥,「设计模式」能解决游戏回放呀,你尝一口!
Unity精华☀️GetInstanceID 和 GetHashCode 的区别