策略模式:定义一系列算法,将每个算法封装起来,并让他们可以相互替换。策略模式让算法可以独立于使用它的客户变化。
策略模式实现了算法定义和算法使用的分离,通过继承和多态的机制实现对算法族的使用和管理。
策略模式很好地解耦了算法的实现与使用。
但是带来的影响是类的膨胀。因此也有不少人说有Lambda表达式就没必要用策略模式了。
个人觉得这是一个误区——
使用策略模式之前应该清楚,这个算法究竟是否可复用
若是可复用的算法,使用面向对象版本的策略模式吧
若是不可复用的算法,用Lambda表达式版本的策略模式就好
二者没有谁强
在你的游戏中,有很多类型的NPC。
有的会飞,有的会有用,有的会跳跃,有的只会研直线走。
需要你用代码实现CalcNavPoints()方法,返回寻路路径。
直接跳过继承四个FlyNPC,SwimingNPC,JumpNPC,StraightNPC类的想法啊……多用组合而不是继承,记住这个原则。
可以考虑把这四个方法都写成private方法,在Npc里实现。
根据NPC的对象决定具体用哪个方法。
public class NPC : MonoBehaviour
{
public NPCType npcType;
void Start()
{
}
public Vector3[] GetNavPath()
{
switch (npcType)
{
case NPCType.Fly:
return GetFlyPath();
case NPCType.Swim:
return GetSwimPath();
case NPCType.Jump:
return GetJumpPath();
case NPCType.StraightWalk:
return GetStraightWalkPath();
default:
return null;
}
}
private Vector3[] GetFlyPath()
{
return null;
}
private Vector3[] GetSwimPath()
{
return null;
}
private Vector3[] GetJumpPath()
{
return null;
}
private Vector3[] GetStraightWalkPath()
{
return null;
}
}
能实现需求,但:
- 对修改不友好。策划要求跳过一些swim的路径点,你得改NPC这个类。
- NPC类负担过重。NPC还要承担很多职能,光实现寻路就轻轻松松几百行。
你应该能想到了,我们要把寻路的这些算法给分拆出去。
我们可以考虑用一个NavHelper类来承载这部分逻辑。
这可以解决NPC类负担过重的问题,但没解决第一个问题—要修改一种寻路,依旧要改整个NPCHelper。
现在我们尝试引进策略者模式将寻路的算法与寻路的调用解耦,并实现寻路算法对拓展开放,对修改关闭。
定义一个抽象算法类AbstractNavPlanner
using UnityEngine;
namespace WenQu.Strategy
{
public abstract class AbstractNavPlanner
{
/// <summary>
/// 获得导航路径
/// </summary>
/// <returns></returns>
public abstract Vector3[] GetNavPath(Vector3 startPos, Vector3 endPos);
}
}
具体算法类距离SwimNavPlanner
namespace WenQu.Strategy
{
public class SwimNavPlanner : AbstractNavPlanner
{
public override Vector3[] GetNavPath(Vector3 startPos, Vector3 endPos)
{
return new Vector3[] { startPos, startPos + endPos / 2 + Vector3.up * -5, endPos };
}
}
}
算法类调用者NPC类
namespace WenQu.Strategy
{
public class NPC
{
private AbstractNavPlanner _navPlanner;
void Start()
{
}
public void SetNavPlanner(AbstractNavPlanner navPlanner)
{
this._navPlanner = navPlanner;
}
public Vector3[] GetNavPath(Vector3 startPos, Vector3 endPos)
{
return _navPlanner.GetNavPath(startPos, endPos);
}
}
}
调用
Vector3 startPos = this.transform.position;
Vector3 endPos = new Vector3(10, 0, 10);
// 飞行npc
NPC theFlyNPC = new NPC();
// 这里可以引入建造者模式来分离构造与调用
theFlyNPC.SetNavPlanner(new FlyNavPlanner());
Vector3[] navPaths = theFlyNPC.GetNavPath(startPos, endPos);
foreach (Vector3 navPath in navPaths)
{
Debug.Log($"flyNpc规划的路径是:{navPath}");
}
至此功能实现。
前面的策略模式实现的很好,是建立在策略可以复用的情况下的。(这些寻路策略对玩家,宠物等同样适用)
很多情况下,我们的一些策略并不需要复用。
那就不需要面向对象提取出那么多类了。
而我们又需要动态地去修改一些算法,怎么办?
可以用Lambda表达式实现策略。
举个例子。
还是类似寻路的要求,要求提供一个SamplePos()方法,功能是把一个点映射到地图上。
只有swimNPC需要被映射到地图下方,其他都是返回原值。
按照策略模式去写的话很麻烦,演示下Lambda表达式。
使用函数委托接收策略
public class LambdaNPC
{
private Func<Vector3, Vector3> _sampleStrategy;
public void SetSampleStrategy(Func<Vector3, Vector3> sampleStrategy)
{
this._sampleStrategy = sampleStrategy;
}
public Vector3 SamplePos(Vector3 pos)
{
return this._sampleStrategy(pos);
}
}
调用
// 调用
LambdaNPC swimNPC2 = new LambdaNPC();
// 传递一个lambda表达式策略
swimNPC2.SetSampleStrategy((pos) =>
{
return new Vector3(pos.x, 0, pos.z);
});
使用Lambda表达式实现策略模式让我们少写了好多类。
是个很好的实践。