Unity精华☀️ 面试官眼中的「设计模式」

本文涉及的产品
实时数仓Hologres,5000CU*H 100GB 3个月
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
实时计算 Flink 版,5000CU*H 3个月
简介: 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);

🟧 观察者模式

定义了对象之间的一对多依赖,

这样一来,当一个对象(被观察者)改变状态时,它的所有依赖(观察者)都会收到通知。

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链接:传送门

好啦,今天的分享就到这里了,

小哥哥们,啊是不是表示一下,一键三连扣一波?

我们下节继续分享两种设计模式,面对面试妥妥的。

相关文章
|
6月前
|
设计模式 前端开发 算法
【面试题】 ES6 类聊 JavaScript 设计模式之行为型模式(二)
【面试题】 ES6 类聊 JavaScript 设计模式之行为型模式(二)
|
设计模式 算法 Java
Java面试题 - 设计模式
Java面试题 - 设计模式
78 0
|
设计模式
【面试题精讲】javaIO设计模式之工厂模式
【面试题精讲】javaIO设计模式之工厂模式
|
设计模式
【面试题精讲】javaIO设计模式之适配器模式
【面试题精讲】javaIO设计模式之适配器模式
|
设计模式
【面试题精讲】javaIO设计模式之装饰器模式
【面试题精讲】javaIO设计模式之装饰器模式
|
1月前
|
设计模式 缓存 Java
面试题:谈谈Spring用到了哪些设计模式?
面试题:谈谈Spring用到了哪些设计模式?
|
2月前
|
设计模式 安全 算法
【Java面试题汇总】设计模式篇(2023版)
谈谈你对设计模式的理解、七大原则、单例模式、工厂模式、代理模式、模板模式、观察者模式、JDK中用到的设计模式、Spring中用到的设计模式
【Java面试题汇总】设计模式篇(2023版)
|
3月前
|
设计模式 算法 Java
面试官:JDK中都用了哪些设计模式?
面试官:JDK中都用了哪些设计模式?
42 0
|
4月前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
78 1