前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Unity精华☀️ 「设计模式」的终极详解!

Unity精华☀️ 「设计模式」的终极详解!

作者头像
星河造梦坊官方
发布2024-08-15 20:06:13
1990
发布2024-08-15 20:06:13
举报
文章被收录于专栏:星河造梦坊专栏

人生不如意十之八九,这个面试啊,免不了会遇到些坎坷,比如说面试官问:

除了这些,还有吗?

那上节我们说了单例模式、观察者模式、代理模式

所以今天呢,橙哥再来和大家好好说道说道:工厂方法、迭代器模式、命令模式。

最后的命令模式,特别适合做回放,回放有Gif演示。

🟥 工厂模式

定义:工厂模式专门负责将大量有共同接口的类实例化。工厂模式可以动态决定实例化哪一个类,而不必实现知道要实例化的是哪一个类。

工厂模式是一个设计模式吗?

不是的,工厂模式分为三种,23种设计模式中,工厂模式就占了两种 ↓

在这个工厂模式家族中有3种形态:

  • 简单工厂模式,这是他的中文名,英文名叫做Simple Factory。(它不属于23种设计方式之一)
  • 工厂方法模式,这是他的中文名,英文名叫做Factory Method。
  • 抽象工厂模式,这是他的中文名,英文名叫做Abstract Factory。

🟧 23种设计模式

设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式 结构型模式,共七种:适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

🟨 简单工厂模式

注意了啊,该模式不属于23种设计模式之一,面试时就不用说了,

但可以在Unity中使用。

简单工厂模式组成:

1)工厂类:工厂类在客户端的直接控制下(Create方法)创建产品对象。 2)抽象产品:是具体产品们的父类,或者是它们共同都继承的接口。抽象产品可以是一个普通类、抽象类(传送门:Abstract)或接口。 3)具体产品:实现抽象产品,定义工厂具体加工出的对象。

接口和抽象类的区别: 一个类可以继承很多个接口,但只能继承一个抽象类

由小老弟就问了,简单工厂模式怎样使用呢?

即先写抽象产品,把产品共同的内容写在一个脚本上

再写具体产品,继承抽象产品,接着写其它代码。因为继承了抽象产品,这样能少些很多代码。

最后写工厂类,供程序调用。输入不同的条件,工厂去调用不同的具体产品,得到不同的产品。

示例:

1️⃣ 抽象产品:Config

代码语言:javascript
复制
public interface Config
{
    /// <summary>
    /// 芯片
    /// </summary>
    void Chip();
}

2️⃣ 具体产品:IPhone

代码语言:javascript
复制
using UnityEngine;

//苹果手机
public class IPhone : Config
{
    public void Chip()
    {
        Debug.Log("使用A14芯片");
    }
}

3️⃣ 具体产品:XiaoMi

代码语言:javascript
复制
using UnityEngine;

//小米手机
public class XiaoMi : Config
{
    public virtual void Chip()
    {
        Debug.Log("使用高通芯片");
    }
}

4️⃣ 工厂类:ConcreteProduct

代码语言:javascript
复制
public class ConcreteProduct
{
    //生产工厂,供外部调用
    public static Config Create(int id)
    {
        switch (id)
        {
            case 1:
                return new XiaoMi();
            case 2:
                return new IPhone();
        }

        return null;
    }
}

🟩 工厂方法模式

工厂方法与简单工厂的区别在于:

工厂方法将工厂类进行了抽象,将实现逻辑延迟到工厂的子类。

不同的产品对应单独的工厂。

下图左图为简单工厂,右图为工厂方法:

书写方法:

先写抽象产品,把产品共同的内容写在一个脚本上

再写具体产品,继承抽象产品,接着写其它代码。因为继承了抽象产品,这样能少些很多代码。

最后写工厂类。与简单工厂模式不同的是,现在工厂类分成了 “抽象工厂脚本”、“具象工厂脚本”。

那现在该怎样使用呢?

现在我们使用该工厂模式的方法是,是直接调用需要的 “具象工厂” 方法,

而不是像简单工厂模式一样,输入条件,得到想要的内容。

下方展示工厂脚本改变的内容,其他脚本跟简单工厂模式相同。

1️⃣ 工厂接口:IFactory

代码语言:javascript
复制
public interface IFactory
{
    /// <summary>
    /// 得到芯片
    /// </summary>
    IConfig Create();
}

2️⃣ 具象工厂:IPhoneFactory

代码语言:javascript
复制
using UnityEngine;

public class IPhoneFactory : IFactory
{
    public IConfig Create()
    {
        Debug.Log("这个工厂生产了 IPhoneAllConfig 配置的苹果手机");
        return new IPhoneAllConfig();
    }
}

3️⃣ 具象工厂:XiaoMiFactory

代码语言:javascript
复制
using UnityEngine;

public class XiaoMiFactory : IFactory
{
    public IConfig Create()
    {
        Debug.Log("这个工厂生产了 XiaoMiAllConfig 配置的小米手机");
        return new XiaoMiAllConfig();
    }
}

🟦 迭代器模式

迭代器模式: 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。

Unity中实现迭代器模式的API是 foreach。

但是,foreach可能不包含我们想要的功能,

下面,我们就来自己实现一个通用的迭代器。

使用方法是:

1、首先自己的迭代器继承基础脚本的类:IEnumerable,可覆写里面的方法。

2、接着就可以使用啦!

1️⃣ 基础类1:Iterator

代码语言:javascript
复制
using System.Collections.Generic;

public class Iterator : IteratorBase
{
    private IList<object> items;

    public int Count => items.Count;

    public Iterator(IList<object> tempItems)
    {
        items = tempItems;
    }

    private int index = -1;

    public object Current => items[index];

    public bool MoveNext()
    {
        return items.Count > ++index;
    }

    public void Reset()
    {
        index = -1;
    }
}

2️⃣ 基础类2:IEnumerable

代码语言:javascript
复制
using System.Collections.Generic;

public interface IteratorBase
{
    object Current { get; }

    int Count { get; }

    bool MoveNext();

    /// <summary>
    /// 将当前指针移动到第一位
    /// </summary>
    void Reset();
}

public class IEnumerable
{
    private IList<object> items = new List<object>();

    public virtual int Count => items.Count;

    public virtual object this[int index]
    {
        get { return items[index]; }
        set { items.Insert(index, value); }
    }

    public virtual IteratorBase GetIterator()
    {
        return new Iterator(items);
    }
}

3️⃣ 迭代器示例:Group

继承IEnumerable就好,Group便已实现了迭代器模式

你可以重写、拓展你的迭代器,实现想要的功能

代码语言:javascript
复制
using UnityEngine;

public class Group : IEnumerable
{
    public override IteratorBase GetIterator()
    {
        Debug.Log("你可以重写你的迭代器");
        return base.GetIterator();
    }
}

下面是最后一步,有的同学别睡觉,敲黑板

4️⃣ 使用示例:Test

代码语言:javascript
复制
using UnityEngine;

public class Test : MonoBehaviour
{
    private void Start()
    {
        Group myGroup = new Group();
        myGroup[0] = "s";
        myGroup[0] = "k";
        myGroup[0] = "o";
        myGroup[0] = "d";
        myGroup[0] = "e";

        print(myGroup.Count);
        
        IteratorBase iterator = myGroup.GetIterator();
        
        print(iterator.Count);
        while (iterator.MoveNext())
        {
            print("当前元素是:" + iterator.Current);
        }
    }
}

🟪 命令模式

命令模式是游戏中很有用的设计模式,书中有一句话是这样说的:

Encapsulate a request as an object, thereby letting users parameterize clients with different requests, queue or log requests, and support undoable operations. —《Design Patterns: Elements of Reusable Object-Oriented Software》

意思是:命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象,同时支持可撤消的操作。

适用于:

  • Unity画画游戏的撤销、重做
  • 小时候推箱子游戏的撤销操作、
  • 五子棋的悔棋操作...

这个模式的特点是:

  • 提供撤销操作(或者还有重做)
  • 将输入命令封装成对象(方法):即从Update里面检测,拿到了一个方法里面,在Update里调用。

1️⃣ 效果演示

点击录制后,我用的WASD操作cube移动

点击回放后,cube自动运动,演示回放。

下面我们来看一下示例脚本有哪些:

1️⃣ 基础接口:command

代码语言:javascript
复制
/// <summary>
/// 供其他物体继承,实现不同功能的执行、撤销、重做功能
/// </summary>
public class command
{
    protected float _time;

    /// <summary>
    /// 录制用到了时间。
    /// 那些PS的撤销操作、推箱子的撤销操作等,就不需要时间了
    /// </summary>
    public float time => _time;

    public virtual void Execute(BoxEntity avator)
    {
    }

    public virtual void Undo(BoxEntity avator)
    {
    }
    
    public virtual void Redo(BoxEntity avator)
    {
    }
}

2️⃣ 盒子执行的命令:BoxCommand

继承了command,并进行了重写。

在后续工程中,我们可能不仅盒子的录制要用命令模式,同一个工程还有画画模块,那画画模块也继承command

这样我们就可以通过统一的接口command,去调用任意实现了command的盒子录制、画画撤销了

代码语言:javascript
复制
using UnityEngine;

public class BoxCommand : command
{
    Vector3 _trans;

    public BoxCommand(Vector3 m, float t)
    {
        _trans = m;
        _time = t;
    }

    public override void Execute(BoxEntity avator)
    {
        avator.move(_trans);
    }

    public override void Undo(BoxEntity avator)
    {
        avator.move(-_trans);
    }
}

3️⃣ 要控制撤销重做的物体:BoxEntity

我们有了命令,也要有命令要控制的对象。

现在就把BoxEntity挂载到要控制的对象身上,并且根据需要,该脚本中有移动、或者隐藏显示、颜色变化等等的实际状态命令。

这些命令供BoxCommand去调用。

代码语言:javascript
复制
using UnityEngine;

/// <summary>
/// 挂载到实体身上,控制实体的运动
/// </summary>
public class BoxEntity : MonoBehaviour
{
    Transform _transform;

    void Start()
    {
        _transform = transform;
    }

    public void move(Vector3 T)
    {
        _transform.Translate(T);
    }
}

4️⃣ BoxTest

该脚本封装了输入命令,并在Update实时检测;

有栈函数,执行了操作后就存上;

有开始记录、开始演示回放的方法,供程序调用。

代码语言:javascript
复制
using System;
using UnityEngine;
using System.Collections.Generic;

public class BoxTest : MonoBehaviour
{
    //待操作对象
    public BoxEntity boxEntity;

    //保存的操作序列
    //这儿如果增为两个栈:撤销栈与重做栈,那么便可在撤销时入重做栈,重做时入撤销栈。完成类似PS的操作。
    Stack<command> commandStack = new Stack<command>();
    
    //当前记录的时间节点
    float recordTime;

    //当前操作模式:无操作、录制、回放
    private RecoderState recoderState = RecoderState.None;

    void Update()
    {
        switch (recoderState)
        {
            case RecoderState.None:
                break;
            case RecoderState.Record:
                Record();
                break;
            case RecoderState.PlayBack:
                PlayBack();
                break;
        }
    }
    
    private enum RecoderState
    {
        None,
        Record,
        PlayBack
    }
    
    /// <summary>
    /// 切换到回放模式,挂载到Button上
    /// </summary>
    public void callBack()
    {
        recoderState = RecoderState.PlayBack;
    }

    /// <summary>
    /// 切换到记录模式,挂载到Button上
    /// </summary>
    public void run()
    {
        recoderState = RecoderState.Record;
    }

    /// <summary>
    /// 控制对象运行,记录命令
    /// </summary>
    void Record()
    {
        recordTime += Time.deltaTime;
        
        //得到当前帧是否操作了命令
        command cmd = InputHandler();
        if (cmd != null)
        {
            //记录当前执行的命令
            commandStack.Push(cmd);
            //去执行
            cmd.Execute(boxEntity);
        }
    }

    /// <summary>
    /// 回放操作
    /// </summary>
    void PlayBack()
    {
        recordTime -= Time.deltaTime;
        
        //返回在堆栈顶部的物体。(不移除)
        if (commandStack.Count > 0 && recordTime < commandStack.Peek().time)
        {
            commandStack.Pop().Undo(boxEntity);
        }
    }

    /// <summary>
    /// 根据输入获取操作命令
    /// </summary>
    command InputHandler()
    {
        if (Input.GetKey(KeyCode.W))
            return new BoxCommand(new Vector3(0, 0.1f, 0), recordTime);
        if (Input.GetKey(KeyCode.S))
            return new BoxCommand(new Vector3(0, -0.1f, 0), recordTime);
        if (Input.GetKey(KeyCode.A))
            return new BoxCommand(new Vector3(-0.1f, 0, 0), recordTime);
        if (Input.GetKey(KeyCode.D))
            return new BoxCommand(new Vector3(0.1f, 0, 0), recordTime);
        return null;
    }
}

甭管你现在有没有跳槽升职的想法,赶紧先备着,

面试前天背一背,对吧?

大家还有什么问题,欢迎在下方留言!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-09-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 🟥 工厂模式
  • 🟧 23种设计模式
  • 🟨 简单工厂模式
    • 1️⃣ 抽象产品:Config
      • 2️⃣ 具体产品:IPhone
        • 3️⃣ 具体产品:XiaoMi
          • 4️⃣ 工厂类:ConcreteProduct
          • 🟩 工厂方法模式
            • 1️⃣ 工厂接口:IFactory
              • 2️⃣ 具象工厂:IPhoneFactory
                • 3️⃣ 具象工厂:XiaoMiFactory
                • 🟦 迭代器模式
                  • 1️⃣ 基础类1:Iterator
                    • 2️⃣ 基础类2:IEnumerable
                      • 3️⃣ 迭代器示例:Group
                        • 4️⃣ 使用示例:Test
                        • 🟪 命令模式
                          • 1️⃣ 效果演示
                            • 1️⃣ 基础接口:command
                              • 2️⃣ 盒子执行的命令:BoxCommand
                                • 3️⃣ 要控制撤销重做的物体:BoxEntity
                                  • 4️⃣ BoxTest
                                  领券
                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档