人生不如意十之八九,这个面试啊,免不了会遇到些坎坷,比如说面试官问:
除了这些,还有吗?
那上节我们说了单例模式、观察者模式、代理模式
所以今天呢,橙哥再来和大家好好说道说道:工厂方法、迭代器模式、命令模式。
最后的命令模式,特别适合做回放,回放有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; } }
甭管你现在有没有跳槽升职的想法,赶紧先备着,
面试前天背一背,对吧?