经典设计模式
一句话记忆:创建型管「new」,结构型管「组装」,行为型管「协作」。
1. 创建型
1.1 单例模式 Singleton
概念: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
大白话: 全公司只有一个老板。谁来找「老板」见的都是同一个人;你想再新造一个?门都没有——只能从前台这一个入口进去。
解决什么痛点: 有些东西全局只需要一份(配置文件、游戏管理器、日志),如果人人都能 new 一个,就会状态对不上、资源浪费。
使用场景:
UE:
- ① GameInstance 跨关卡全局:引擎自带单例,直接继承
UGameInstance类,在 Project Settings 里指定,之后用GetGameInstance<UMyGameInstance>()获取。 - ② GameMode 单局规则:继承
AGameModeBase,一局游戏只有一个实例,用GetWorld()->GetAuthGameMode()获取。 - ③ 自定义 SaveManager 存档管理:引擎没有现成单例,自己写一个类,内部放
static USaveManager* Instance指针和一个Get()静态方法。
Unity:
- ① GameManager 游戏总控:写一个继承
MonoBehaviour的类,加public static GameManager Instance静态属性,在Awake()里赋值为this,之后全局任意处用GameManager.Instance.xxx调用。 - ② AudioManager 音效管理:和 GameManager 一样做成单例,类里封装一个
Play(string clip)方法,全局AudioManager.Instance.Play("shot")播放音效。 - ③ UIManager 界面管理:单例持有各面板引用,封装
Open(string panel)方法,UIManager.Instance.Open("背包")统一开关界面。
1.2 工厂方法模式 Factory Method
概念: 定义一个创建对象的抽象方法(工厂方法),让子类重写它来决定实例化哪一个类,从而使一个类的实例化延迟到其子类。
大白话: 你想喝咖啡,只要喊一声「来杯咖啡」,不用管后厨到底怎么做。具体是美式还是拿铁,由后厨(工厂)决定。
解决什么痛点: 调用方不想关心「具体 new 哪个类」,把创建决定权交给专门的工厂;以后要加新类型,只加一个工厂,不用改调用方。
使用场景:
UE:
- ① 封装 SpawnActor 的刷怪工厂:写一个抽象工厂类声明纯虚
Create(),子类(如ZombieFactory)里用SpawnActor生成具体怪物返回。 - ② 按难度产不同 Boss:
ABossFactory声明纯虚Create(),AHardFactory重写返回NewObject<AHardBoss>()。 - ③ 子弹/投射物工厂:
AProjectileFactory声明纯虚Create(),ABulletFactory重写生成子弹。
Unity:
- ① 按关卡生成不同敌人:定义抽象
EnemyFactory,声明抽象方法Create(),每个关卡写一个子类(如Level1Factory),重写它决定返回new Goblin()还是new Dragon()。 - ② 道具掉落工厂:定义抽象
DropFactory,BossDropFactory重写Create()返回new LegendarySword()这类掉落。 - ③ 技能实例工厂:定义抽象
SkillFactory,子类FireBallFactory重写Create()返回new FireBall()。
1.3 抽象工厂模式 Abstract Factory
概念: 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
大白话: 养宠物。猫是一套(猫+鱼),狗是一套(狗+骨头),选哪个工厂就整套出哪个,不会出现「猫啃骨头」这种错配。
解决什么痛点: 需要成组地创建「一整套互相配套」的对象,保证它们风格一致,还能整套切换。
使用场景:
UE:
- ① 阵营兵营:
IBarracks声明CreateMelee()/CreateRanged(),人族/兽族兵营各产一整套同阵营单位。 - ② 主题 UI:
IThemeFactory声明CreateButton()/CreatePanel(),换主题整套切换。 - ③ 角色套装:
ICharacterFactory声明CreateBody()/CreateOutfit(),保证套装部件配套。
Unity:
- ① 敌人阵营工厂:
IEnemyFactory声明CreateMelee()/CreateRanged(),不同阵营产配套单位。 - ② UI 皮肤套装:
IUIFactory声明CreateButton()/CreateSlider(),整套皮肤切换。 - ③ 宠物系统:
IPetFactory声明CreatePet()/CreateFood(),猫配鱼、狗配骨头不混搭。
1.4 建造者模式 Builder
概念: 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
大白话: 组装电脑。机箱、CPU、显卡分开挑,最后喊一句「装起来」。不用一进门就报出全部配置。
解决什么痛点: 对象构造参数太多、还有可选参数,直接 new 会出现一长串参数和一堆 null,可读性极差。
使用场景:
UE:
- ① FActorSpawnParameters 配置 Actor:引擎内建,填
FActorSpawnParameters结构体的Owner、SpawnCollisionHandlingOverride等字段,再传给SpawnActor。 - ② 构建任务/副本配置:写
FQuestBuilder,Name()/Reward()返回*this链式,最后Build()产出任务配置。 - ③ 拼装武器属性:
FWeaponBuilder提供Damage()/Speed()链式方法,最后Build()产出武器属性。
Unity:
- ① 复杂角色配置(多可选参数):写一个
CharacterBuilder类,每个配置方法(Name()/Health()/Weapon())都返回this支持链式调用,最后Build()产出角色对象。 - ② 拼装复杂 UI 面板:
PanelBuilder提供AddButton()/AddInput()链式方法,最后Build()组装出面板。 - ③ 构建存档数据对象:
SaveDataBuilder提供Level()/Coins()链式方法,最后Build()产出存档对象。
1.5 原型模式 Prototype
概念: 用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
大白话: 复印机。要一份新文件,不用重新手写,拿原件复印一份就行。
解决什么痛点: 对象创建成本高(要读磁盘、算半天),或者想基于已有对象的状态做副本,直接 new 再逐个赋值很麻烦。
使用场景:
UE:
- ① CDO+SpawnActor:每个类都有默认对象(CDO)作原型,用
SpawnActor从它克隆实例。 - ② DataAsset 复用:用
UDataAsset配好一份模板,多处复用。 - ③ DuplicateActor 复制已有 Actor:用
DuplicateObject复制现有对象。
Unity:
- ① Prefab+Instantiate 克隆:把敌人做成 Prefab 当原型,用
Instantiate(enemyPrefab, pos, rot)复制出实例。 - ② 对象池克隆子弹/敌人:纯 C# 对象用
MemberwiseClone()从池里的原型快速复制。 - ③ 从 Boss 模板克隆精英怪:给类加一个
Clone()方法(内部用MemberwiseClone()),克隆后再微调属性。
2. 结构型
2.1 适配器模式 Adapter
概念: 将一个类的接口转换成客户希望的另一个接口,使原本因接口不兼容而不能一起工作的类可以一起工作。
大白话: 出国用转接头。外国插座是圆孔,你的插头是扁的,插不上?加个转接头就通了——不用改插座,也不用改插头。
解决什么痛点: 对接第三方库 / 旧代码,接口对不上,但对方源码你改不了,只能加一层「转换」。
使用场景:
UE:
- ① 对接第三方插件统一接口:定义
ILeaderboard接口,FSteamAdapter实现它并内部调 Steam API。 - ② 不同平台文件读写适配:定义
IFileIO接口,FAndroidFileIO/FWinFileIO各自实现不同平台的读写。 - ③ 兼容旧版 API:定义新接口,写适配器包装旧 API。
Unity:
- ① 接入广告 SDK 统一接口:定义
IAdSDK接口,写AdmobAdapter实现它、内部调 Admob,业务代码只面向IAdSDK,换 SDK 只换 Adapter。 - ② 接入支付 SDK 统一接口:定义
IPaySDK接口,WechatPayAdapter内部调微信支付 API。 - ③ 新旧版 API 兼容层:定义新接口
INewAudio,写适配器包装旧的OldAudioAPI,对外提供新接口。
2.2 桥接模式 Bridge
概念: 将抽象部分与它的实现部分分离,使它们都可以独立地变化。
大白话: 遥控器和电视。遥控器管「开关/调台」,电视管「显示」,两者分开。换遥控器不用换电视,换电视不用换遥控器,还能自由配对。
解决什么痛点: 两个维度会各自变化(比如「形状」和「渲染方式」),如果让它们绑死,就得为每种组合写一个类(3 形状 × 3 渲染 = 9 个类),爆炸。
使用场景:
UE:
- ① 渲染后端抽象:
IRHI接口,FD3D12RHI/FVulkanRHI各自实现不同渲染后端。 - ② 平台抽象层:
IPlatform接口,FWindowsPlatform/FAndroidPlatform各自实现。 - ③ 角色 × 攻击方式:
IAttack接口(近战/远程),ACharacter持有IAttack指针。
Unity:
- ① 形状 × 渲染器:
IRenderer接口(矢量/栅格渲染),Shape抽象类持有一个IRenderer引用,Circle继承 Shape,形状和渲染方式各自独立组合。 - ② 设备 × 输入方式:
IInput接口(键盘/手柄/触屏),Device抽象类持有IInput引用。 - ③ 角色 × 移动方式:
IMove接口(走/飞/游泳),Hero持有IMove引用。
2.3 组合模式 Composite
概念: 将对象组合成树形结构以表示「部分—整体」的层次结构,使客户对单个对象和组合对象的使用具有一致性。
大白话: 文件夹。文件夹里既能放文件,也能放文件夹。你不管是双击文件还是文件夹,操作方式都一样。
解决什么痛点: 树形层级结构(文件系统、UI 面板、场景图),让「单个对象」和「一堆对象」能被统一对待,不用写一堆 if 判断是叶子还是容器。
使用场景:
UE:
- ① Actor 组件树:用
NewObject<UActorComponent>后RegisterComponent()把组件挂到 Actor 上。 - ② 关卡对象层级:用
AttachToActor把子 Actor 挂到父 Actor 下。 - ③ Buff 叠加链:
UBuff类里放TArray<UBuff*> Children嵌套。
Unity:
- ① Transform 父子层级树:引擎内建,用
child.transform.SetParent(parent.transform)把子物体挂到父物体下。 - ② UI 面板嵌套:定义
IUIComponent接口,UIPanel(容器,含子元素列表)和UIButton(叶子),容器的Render()里递归调用子元素。 - ③ 技能树:
SkillNode类里放List<SkillNode> children形成树形结构。
2.4 装饰器模式 Decorator
概念: 动态地给一个对象添加一些额外的职责,就扩展功能而言,装饰器比生成子类更灵活。
大白话: 点奶茶。基础奶茶 5 块,加珍珠 +2、加奶盖 +3、加椰果 +1,层层往上加,每加一样都是在原有基础上「包一层」。
解决什么痛点: 给已有功能叠加「可选能力」,如果靠继承,每种组合都要建一个子类(珍珠奶茶、椰果奶茶、珍珠椰果奶茶……),会爆炸。
使用场景:
UE:
- ① 给 Actor 动态 AddComponent 叠加能力:
NewObject<UFlameComponent>后RegisterComponent()动态加火焰能力。 - ② 技能修饰器:
ISkill接口+装饰器,在Power()里叠加修饰。 - ③ 伤害修正链:
IDamage接口+装饰器,FCritDecorator把伤害乘 2。
Unity:
- ① 武器叠 Buff:
IWeapon接口+抽象WeaponDecorator(持有一个inner),FireDecorator重写Damage()在 inner 基础上加火焰伤害。 - ② 移动叠加缓速/加速:
IMove接口+装饰器,SlowDecorator把 inner 的速度乘 0.5。 - ③ 伤害附加额外效果:
PoisonDecorator在 inner 伤害上叠加中毒值。
2.5 外观模式 Facade
概念: 为子系统中的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
大白话: 餐厅点餐。你跟服务员说「来份牛排」,服务员帮你对接后厨、配菜、收银。你只需要面对一个服务员,不用知道后厨有多乱。
解决什么痛点: 一个功能要协调多个复杂子系统,如果让调用方直接面对它们,就得搞清楚每个子系统怎么用、调用顺序是什么,耦合一大堆、还容易错。
使用场景:
UE:
- ① GameMode 协调各系统:
AMyGameMode里写一个StartGame()方法,内部串起生成玩家、开始对局。 - ② 统一网络门面:
FNetFacade的Connect()内部建会话、加入房间。 - ③ 存档/读档门面:
FSaveFacade的SaveGame()内部保存玩家、世界、背包。
Unity:
- ① GameManager 统一封装音频/场景/存档:写一个
GameFacade,内部持有各子系统,StartGame()一个方法串起整套初始化。 - ② 一键启动/退出流程:
AppFacade的Boot()内部依次初始化配置、网络、UI。 - ③ 统一 UI 入口:
UIFacade的ShowMainMenu()内部协调多个面板。
2.6 享元模式 Flyweight
概念: 运用共享技术有效地支持大量细粒度的对象。
大白话: 共享单车。车不是你的,是共享的,你只是临时骑一下。一万个人骑,不需要一万辆车。
解决什么痛点: 海量相似对象(森林里的树、游戏里的子弹),如果每个都存完整数据,内存爆掉。把「大家都一样」的部分抽出来共享。
使用场景:
UE:
- ① InstancedStaticMesh 批量渲染:用
UInstancedStaticMeshComponent的AddInstance共享一份 Mesh 加 N 个实例。 - ② 纹理图集:多张小图打包成一张大图共享纹理。
- ③ 海量道具共享 Mesh:N 个道具引用同一个 StaticMesh 资源。
Unity:
- ① 海量子弹共享材质:把不变属性(材质、速度)抽成
BulletType共享,每颗子弹只存位置、方向。 - ② GPU Instancing 草地树木:用
Graphics.DrawMeshInstanced共享一份 Mesh/Material 画 N 份。 - ③ 字体图集/字符缓存:字形数据共享,每个字符只存索引。
2.7 代理模式 Proxy
概念: 为其他对象提供一种代理以控制对这个对象的访问。
大白话: 明星的经纪人。你要找明星,得先通过经纪人,经纪人可以在你见明星之前先挡一道。
解决什么痛点: 访问一个对象时,要在真正访问前后加控制——延迟加载、权限校验、远程转发、日志记录。
使用场景:
UE:
- ① 资源懒加载:软引用代理先持有,真正用到才
LoadSynchronous()加载。 - ② 远程玩家代理:本地
RemotePlayerProxy把操作 RPC 转发到服务器。 - ③ 保护代理:存档系统代理,只有 GM 能写。
Unity:
- ① 资源懒加载:
ProxyImage先不加载,第一次Display()才真正加载。 - ② 保护代理:配置只读代理,运行时代码不能改。
- ③ 远程代理:网络同步对象代理,本地调用转发到服务器。
3. 行为型
3.1 责任链模式 Chain of Responsibility
概念: 使多个对象都有机会处理请求,从而避免请求发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递请求,直到有一个对象处理它为止。
大白话: 请假审批。请 1 天假组长就能批,请 5 天得经理批,请 15 天得总监批。请求从组长开始往上递,谁有权限谁批。
解决什么痛点: 发送方不想硬编码「这事到底该谁处理」,把处理者串成一条链,请求沿链传递,能处理的人处理掉。
使用场景:
UE:
- ① 输入处理链:
IInputHandler含Next指针,Handle()里消费或传给下一级。 - ② 碰撞/伤害过滤链:
IDamageFilter含Next,逐个过滤。 - ③ 消息传递链:
IMessageHandler含Next。
Unity:
- ① 伤害结算链:
DamageHandler抽象类含next引用和SetNext(),ShieldHandler处理完或交给 next(护盾→护甲→血量→死亡)。 - ② 事件处理链:
EventHandler抽象类,每个处理者处理不了就传给 next。 - ③ 技能效果结算链:一次攻击依次结算暴击→吸血→护盾→伤害,每个效果一个处理器。
其他领域:
- ① 金融风控链:额度校验→黑名单→反欺诈→放款(本节的代码示例)。
- ② Web 中间件:认证→日志→限流→业务,每个中间件是链上的一环。
- ③ OA 审批流:组长→经理→总监→总经理,逐级审批。
3.2 命令模式 Command
概念: 将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
大白话: 点外卖下单。订单是一个「对象」,可以下、可以取消、可以重下。你点按钮,按钮不关心后端是做饭还是做饮料。
解决什么痛点: 把「请求」打包成对象,让「发命令的人」和「执行的人」解耦,还能支持撤销、排队、记录日志、回放。
使用场景:
UE:
- ① 输入绑定命令:
ICommand接口,FJumpCommand实现,按键触发Execute()。 - ② 命令队列:
TArray<ICommand*>,循环Execute()。 - ③ 回放系统:
FReplayCommand含时间戳,按时间重现。
Unity:
- ① 关卡编辑器撤销/重做:
ICommand接口含Execute()/Undo(),PlaceBlock实现它,用 undo 栈和 redo 栈做撤销重做。 - ② 技能队列:
CastSkill实现ICommand,队列里依次Execute()。 - ③ 按键映射+战斗回放:
MoveCommand把输入封装成命令,可回放。
3.3 解释器模式 Interpreter
概念: 给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
大白话: 翻译官。拿到一句话,按语法规则把它拆开、逐部分理解,最后得出结果。比如「1 + 2」要拆成「1」「+」「2」再算。
解决什么痛点: 要解释执行某种固定语法(公式计算、规则引擎、SQL 解析),把语法规则用类表达出来。
使用场景:
UE:
- ① 对话树条件节点:
IExprNode含Evaluate(),FHasQuestNode实现。 - ② 蓝图节点求值:蓝图节点本质就是解释器。
- ③ 任务条件表达式:
FConditionNode含IsTrue()。
Unity:
- ① 脚本化技能描述:
IExpr接口含Eval(),NumExpr/AddExpr组合解释执行。 - ② 对话系统脚本:
ICondition接口含IsTrue(),HasItemCondition实现。 - ③ 任务条件/触发器解析:
ITrigger接口含Match(),KillCountTrigger实现。
3.4 迭代器模式 Iterator
概念: 提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露该对象的内部表示。
大白话: 一袋豆子。不管豆子装在小盒子还是大麻袋里,你都能「一颗一颗」倒出来数,不用管容器内部长啥样。
解决什么痛点: 不同集合(数组、列表、树、图)用同一种方式遍历,客户端不关心内部结构。C# 里已经内建成 foreach。
使用场景:
UE:
- ① TArray 遍历 Actor:
for (AActor* A : ActorArray)。 - ② 遍历组件列表:
for (UActorComponent* Comp : Actor->GetComponents())。 - ③ 遍历关卡对象:用
TActorIterator遍历。
Unity:
- ① foreach 遍历敌人列表:
foreach (Enemy e in enemies)。 - ② 遍历背包:
foreach (Item item in inventory)。 - ③ 遍历子物体 Transform:
foreach (Transform child in transform)。
3.5 中介者模式 Mediator
概念: 用一个中介对象来封装一系列的对象交互,使各对象不需要显式地相互引用,从而使其耦合松散,并且可以独立地改变它们之间的交互。
大白话: 群聊。你发一条消息,群里所有人都能看到。你不需要一个个去私聊通知。
解决什么痛点: 多个对象互相通信会形成「网状」依赖,改一个牵动一片。加一个中介者,变成「大家都只跟中介者说话」。
使用场景:
UE:
- ① GameMode 居中协调:
AMyGameMode的NotifyAll()协调各 Actor。 - ② 事件系统:UE 的 EventSystem/MessageBus 就是中介者。
- ③ 多人会话协调:
FSessionMediator广播给所有玩家。
Unity:
- ① 复杂 UI 联动:
UIMediator持有面板列表,Notify()统一转发,面板之间不互相引用。 - ② 多人聊天室:
ChatRoom持有用户列表,Send()转发给其他用户。 - ③ 战斗事件中心:
EventCenter居中转发,各单位不互相引用、只认中心(内部的通知才是用Publish()广播,那是观察者机制)。
3.6 备忘录模式 Memento
概念: 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以后就可将该对象恢复到原先保存的状态。
大白话: 游戏存档。玩到一半存个档,之后不管怎么作死,读档就能回到存档那一刻。
解决什么痛点: 要保存并恢复对象状态(撤销、快照、存档),但不破坏封装。
使用场景:
UE:
- ① SaveGame 系统:继承
USaveGame,用SaveGameToSlot/LoadGameFromSlot存档读档。 - ② 回放快照:
FSnapshot保存变换、血量。 - ③ 撤销/重做状态:
TArray<FSnapshot>栈。
Unity:
- ① 游戏存档读档:
SaveData类保存状态,Player.Save()生成快照、Restore()恢复。 - ② 角色状态快照/回滚:
StateSnapshot保存位置、血量,Capture()/Restore()做快照和回滚。 - ③ 编辑器撤销历史:操作前把快照压入 undo 栈。
3.7 观察者模式 Observer
概念: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
大白话: 公众号关注。你关注了某个号,它一发文,你和所有粉丝的微信自动弹通知。你不用天天去刷它。
解决什么痛点: 一个对象状态变了,要自动通知所有关心它的对象(UI 绑定数据、事件广播),并且双方解耦。
使用场景:
UE:
- ① Delegate/EventDispatcher:
DECLARE_DYNAMIC_MULTICAST_DELEGATE声明,Broadcast()广播。 - ② UI 绑定属性变化:UMG 的事件绑定/属性绑定。
- ③ 消息通知系统:
Broadcast广播,订阅者自动收到。
Unity:
- ① event 广播:
public static event Action<int> OnScoreChanged,用Invoke广播,订阅者+=订阅。 - ② UI 绑定血量/分数变化:在属性的 setter 里
OnHpChanged?.Invoke()通知 UI。 - ③ 成就系统:
OnAchievement事件,解锁处 Invoke。
3.8 状态模式 State
概念: 允许一个对象在其内部状态改变时改变它的行为,看起来似乎修改了它的类。
大白话: 同一个人,醒着时喊他「吃饭」他会去吃,睡着时喊他「吃饭」他只会翻个身。同一个动作,在不同状态下反应完全不同。
解决什么痛点: 对象行为随内部状态变化而变化,避免写一大坨 if (state == A) ... else if (state == B) ... 的意大利面。
使用场景:
UE:
- ① 行为树:每个 Task 就是一个状态节点。
- ② 状态机(待机/攻击/巡逻):
IState含Tick(),各状态实现。 - ③ 动画状态机:AnimInstance 的状态机节点。
Unity:
- ① 角色状态机:
IState接口(Enter/Update/Exit),StateMachine.Change()切换,IdleState/AttackState各自实现。 - ② AI 状态机:
PatrolState/ChaseState实现IState。 - ③ Animator 动画状态机:引擎内建,
animator.SetTrigger("Attack")切换动画状态。
3.9 策略模式 Strategy
概念: 定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换,使算法可独立于使用它的客户而变化。
大白话: 出门上班。可以打车、坐地铁、骑共享单车,目的地一样,但「方式」可以随时换,你按当下情况选一个。
解决什么痛点: 同一件事有多种算法/做法,要能在运行时灵活切换,而不是堆一堆 if-else。
使用场景:
UE:
- ① 行为树 Task:每个 BTTask 封装一种行为,黑板切换。
- ② 导航策略:
INavStrategy接口。 - ③ 难度/AI 策略:
IAIStrategy接口,FEasyAI/FHardAI实现。
Unity:
- ① AI 行为切换:
IBehavior接口,Patrol/Chase实现,Enemy.SetBehavior()运行时切换。 - ② 寻路算法:
IPathFind接口,AStar/Dijkstra实现。 - ③ 攻击方式/难度策略:
IAttack接口,EasyAttack/HardAttack实现。
3.10 模板方法模式 Template Method
概念: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中,使子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
大白话: 泡面和煮饺子。流程都是「烧水 → 下锅 → 捞起」,只有「下锅」这一步不同。骨架一样,细节不同。
解决什么痛点: 算法整体流程固定,但个别步骤各家不一样。父类定好骨架,子类只填空。
使用场景:
UE:
- ① Actor 生命周期:重写
BeginPlay()/Tick(),引擎按固定流程调用。 - ② 网络复制流程:重写
GetLifetimeReplicatedProps()。 - ③ 关卡初始化流程:固定流程+可重写步骤。
Unity:
- ① 游戏主流程骨架:
GameFlow的Run()定好流程(初始化→循环更新→结束),子类只重写各个步骤。 - ② AI 行为框架:
BaseEnemy.Attack()定骨架(准备→出手→收招),子类只填DoAttack()。 - ③ 数据加载流程:
DataLoader.Load()定流程,子类只填Parse()。
3.11 访问者模式 Visitor
概念: 表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
大白话: 税务局上门查账。不同商家(书店、水果店)接待税务的方式不同,但来查账的「税种」可以不断增加,商家自己不用改。
解决什么痛点: 对象结构相对稳定,但经常要给这组对象加「新操作」。把操作抽成访问者,加操作 = 加一个访问者,不动原对象。
使用场景:
UE:
- ① 序列化:重写
Serialize(FArchive&)。 - ② 遍历组件/对象图:
IObjectVisitor接口。 - ③ 场景统计/导出:
FExportVisitor实现IObjectVisitor。
Unity:
- ① 场景遍历统计:
IVisitor接口+IElement.Accept(),CountVisitor统计,不用改元素类。 - ② 渲染遍历:
RenderVisitor对不同元素做渲染。 - ③ 技能对不同目标类型施加效果:
DamageVisitor对敌人减血、对道具忽略。