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

目录

🟥 工厂模式

🟧 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


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

除了这些,还有吗?

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

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

最后的命令模式,特别适合做回放,回放有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;
    }
}

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

面试前天背一背,对吧?

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值