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

本文涉及的产品
智能开放搜索 OpenSearch行业算法版,1GB 20LCU 1个月
实时计算 Flink 版,5000CU*H 3个月
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
简介: Unity精华☀️ 「设计模式」的终极详解!

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

除了这些,还有吗?

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

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

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

🟥 工厂模式

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

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

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

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

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


🟧 23种设计模式

设计模式分为三大类:

创建型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式

结构型模式,共七种:适配器模式、装饰者模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

🟨 简单工厂模式

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

但可以在Unity中使用。

简单工厂模式组成:

1)工厂类:工厂类在客户端的直接控制下(Create方法)创建产品对象。

2)抽象产品:是具体产品们的父类,或者是它们共同都继承的接口。抽象产品可以是一个普通类、抽象类(传送门:Abstract)或接口。

3)具体产品:实现抽象产品,定义工厂具体加工出的对象。

接口和抽象类的区别:

一个类可以继承很多个接口,但只能继承一个抽象类

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


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

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

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

示例:

1️⃣ 抽象产品:Config

public interface Config
{
    /// <summary>
    /// 芯片
    /// </summary>
    void Chip();
}

2️⃣ 具体产品:IPhone

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

3️⃣ 具体产品:XiaoMi

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

4️⃣ 工厂类:ConcreteProduct

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

public interface IFactory
{
    /// <summary>
    /// 得到芯片
    /// </summary>
    IConfig Create();
}

2️⃣ 具象工厂:IPhoneFactory

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

3️⃣ 具象工厂:XiaoMiFactory

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

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

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便已实现了迭代器模式

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

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

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


4️⃣ 使用示例:Test

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

/// <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的盒子录制、画画撤销了

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去调用。

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实时检测;

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

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

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;
    }
}

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

面试前天背一背,对吧?

相关文章
|
2月前
|
API 图形学
Unity精华☀️GetInstanceID 和 GetHashCode 的区别
Unity精华☀️GetInstanceID 和 GetHashCode 的区别
|
2月前
|
设计模式 安全 图形学
Unity精华☀️ 面试官眼中的「设计模式」
Unity精华☀️ 面试官眼中的「设计模式」
|
2月前
|
图形学
Unity精华☀️点乘、叉乘终极教程:用《小小梦魇》讲解这个面试题~
Unity精华☀️点乘、叉乘终极教程:用《小小梦魇》讲解这个面试题~
|
5月前
|
设计模式 开发者
探索代码之美:我的编程艺术之旅
【5月更文挑战第19天】 在数字的海洋中,我是一位潜水者,每一次键盘的敲击都是对未知世界的探索。本文记录了我在编程实践中的一些感悟和经验,从最初的困惑到最后的豁然开朗,我逐渐理解了编程不仅仅是一种技能,更是一种艺术。我将分享如何通过不断学习和实践,将代码转化为优雅的解决方案,以及在这个过程中所经历的挑战和收获。
|
5月前
|
设计模式 算法 测试技术
探索代码之美:我的编程思考之旅
【5月更文挑战第8天】 在数字化的浪潮中,编程已成为一种艺术,一种用逻辑与创造力编织的语言。本文将分享我在编程实践中的一些技术感悟,从最初的困惑到逐渐的深入理解,再到最后的灵活应用,我经历了一段充满挑战与收获的旅程。文章不仅探讨了编程技巧的提升,还涉及了对软件设计原则的认识,以及如何通过不断学习来适应快速变化的技术环境。
|
5月前
|
设计模式 开发者
第一篇 设计模式引论 - 探索软件设计的智慧结晶
第一篇 设计模式引论 - 探索软件设计的智慧结晶
|
5月前
|
设计模式 存储 缓存
设计模式全览:编程艺术的精髓!
设计模式全览:编程艺术的精髓!
38 0
|
11月前
|
设计模式
设计模式系列教程(完) - 终章总结
设计模式系列教程(完) - 终章总结
29 0
|
设计模式 Java API
听说有人用一个坦克大战项目把23种设计模式讲完了?(附源码)
长期以来给大家分享的都是技术和文档的一些内容,大家应该已经看腻了。今天给大家分享一波java的坦克大战项目和23种设计模式视频吧,让大家来实践一下,希望大家能够喜欢!
|
设计模式 前端开发 JavaScript
图解23种设计模式(TypeScript版)——前端切图崽必修内功心法
图解23种设计模式(TypeScript版)——前端切图崽必修内功心法
图解23种设计模式(TypeScript版)——前端切图崽必修内功心法
下一篇
无影云桌面