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精华☀️四元数(Quaternion)解决万向锁

Unity精华☀️GetInstanceID 和 GetHashCode 的区别

Unity精华☀️GetInstanceID 和 GetHashCode 的区别

Unity精华☀️UI和物体可见性的判断方法

Unity精华☀️UI和物体可见性的判断方法