设计模式是程序设计中非常重要的一个部分。本节课提到的主要设计模式包括单例模式、指令模式、观察者模式和工厂模式等。
单例模式
单例模式一般适用于一些静态类别(例如GameManager、SceneManager之类的)。
静态类实例的生成方法如下:
private static GameManager _inst = null;
public static GameManager => _inst ?? (_inst = new GameManager())
单例模式既有优点又有缺点
优点:
- 单一性
- 便捷性
- 高效节约资源
缺点:
- 全局性在较大的项目里容易出错(主要来源于一些非法更改或者意外更改),风险性比较高。
当然,这个缺点是有解决办法的:使用静态的类方法传参即可。
总而言之,单例模式是最常见的设计模式之一。
指令模式Command Pattern
指令模式一般包括以下部分:
- 指令接口
- 指令类
- 调用者
- 吸收者
- 使用者
当然,简单的指令模式只有调用者和接收者。使用特定的接口定义:
public interface Command
指令接口的实现:一般采用abstract虚基类定义,并且在子类中override虚函数(指令类)。然后,在其他类中直接调用该类的虚方法(即调用者)。
优点:
- 将调用者和执行者解耦,通用模块化
- 可以方便的撤回指令
缺点:
- 类变多了,不利于管理。
这是由于任何的指令模式都定义了一个新的基类。
工厂模式Factory Pattern
将类的实例化过程包装起来,使得使用者可以方便可控地获得类实例的方法。
(比如说,Unity自带的Instantiate()方法是否可以类似一种工厂模式生成GameObject类实例的指令?)
工厂模式还存在一种拓展模式,即抽象工厂模式,抽象工厂生产的实例就是一个工厂Factory类。
观察者模式Spectator Pattern
观察者模式有两个重要的组成部分:观察者和通知者。当通知者发生变化时,向观察者发送信息。
(比如说在Unity中的EventSystem,可以使用AddListener()方法添加观察者,并且在通知者身上绑定通知组件(例如Button))
策略模式Strategy Pattern
在程序运行时根据不同的情况选择不同的算法。在使用策略模式时,定义一系列的算法,并且将他们封装起来而且让他们可以相互替换。使用策略模式的原因是,多种类型的对象需要实现同一个算法,并且算法是带有条件区分的,在这种情况下,使用if-else或者switch会使得代码复杂度增加并且难以维护。
实现策略模式的关键在于实现同一个接口。
优点:
- 算法可以自由切换
- 避免使用多重条件判断
- 延展性良好
缺点: 策略类增多,而且所有的策略类都需要对外暴露。
对象池Object Pool
对象池把所有对象存在池子里。当需要实例时,先寻找有无可以复用的。当销毁物体时,将物体标记为空闲并且扔进对象池内。
使用原因:Instantiate()方法和destroy()方法非常的消耗性能。
对象池的主要思路如下:
- 从对象池内获取一个物体
- 把物体还给对象池。
数据结构设计:
List <Container<T>> list; //池子列表
Dictionary<T,Container<T>> lookup;//池子查找
Func<T> factoryFunc;
int lastIndex = 0;
算法设计:
Spawn:找到物体对应的池子,没有则创建一个池子。
Release:标记lookup中的物体的该物体为空闲,找到该对象对应的对象池然后还回去,标记为空闲,最后SetActive(false)。
*ECS模式(Entity-Component-System)
在ECS模式中,所有的实体都有且仅有一系列的Component。
注意,Entity只保存数据不保存算法。所有的算法都由Entity传入参数道System执行。
(Unity中包含了Entity和Component部分,但是许多算法是定义在GameObject内部的,因此Unity不是ECS系统)
ECS是一种面向数据编程的系统,不关注数据元素,而只关注元素内的数据项。
*(事实上,Unity最近也推出了给予自家引擎的ECS系统)
总结
设计模式是一种应用程序开发时经常考虑的项目,在做架构时应当提前确定设计模式。当然,上述设计模式也不能滥用,应当在实际操作中根据具体情况来选择。