一、策略模式核心动机
算法或行为切换
客户端无需知道具体实现,只需持有策略接口并在运行时赋予不同策略实例。消除条件分支
将复杂的if/else或switch逻辑移入各个策略类,使主流程代码更清晰。开闭原则
新增策略只需添加新的策略类,而不修改现有代码。可测试性
各策略在独立环境下可单元测试,客户端只需 Mock 策略接口。
二、模式定义与 UML 类图

- IAttackStrategy:策略接口,定义统一方法
Execute。 - ConcreteStrategy:
MeleeStrategy、RangedStrategy、MagicStrategy等,实现不同攻击算法。 - Context(Player):持有策略引用,在
Attack时委托给当前策略执行。
三、Unity 实战示例:角色攻击策略
3.1 定义策略接口与实现
public interface IAttackStrategy {
/// <summary>
/// 执行一次攻击,caster 是攻击者 Transform,target 是被攻击对象
/// </summary>
void Execute(Transform caster, GameObject target);
}
public class MeleeStrategy : IAttackStrategy {
public float damage = 10f;
public float range = 2f;
public void Execute(Transform caster, GameObject target) {
if (Vector3.Distance(caster.position, target.transform.position) <= range) {
var health = target.GetComponent<Health>();
health?.TakeDamage(damage);
}
// 可播放近战动画、音效等
}
}
public class RangedStrategy : IAttackStrategy {
public GameObject projectilePrefab;
public float speed = 20f;
public void Execute(Transform caster, GameObject target) {
var go = Object.Instantiate(projectilePrefab, caster.position, Quaternion.identity);
var dir = (target.transform.position - caster.position).normalized;
go.GetComponent<Rigidbody>().velocity = dir * speed;
}
}
public class MagicStrategy : IAttackStrategy {
public GameObject effectPrefab;
public float areaRadius = 3f;
public float damage = 8f;
public void Execute(Transform caster, GameObject target) {
Object.Instantiate(effectPrefab, target.transform.position, Quaternion.identity);
var colliders = Physics.OverlapSphere(target.transform.position, areaRadius);
foreach (var col in colliders) {
col.GetComponent<Health>()?.TakeDamage(damage);
}
}
}
3.2 Context:Player 类
public class Player : MonoBehaviour {
private IAttackStrategy _strategy;
void Start() {
// 默认近战策略
_strategy = new MeleeStrategy();
}
public void SetStrategy(IAttackStrategy strategy) {
_strategy = strategy;
}
public void Attack(GameObject target) {
_strategy.Execute(transform, target);
}
void Update() {
if (Input.GetKeyDown(KeyCode.Alpha1)) {
SetStrategy(new MeleeStrategy());
} else if (Input.GetKeyDown(KeyCode.Alpha2)) {
SetStrategy(new RangedStrategy());
} else if (Input.GetKeyDown(KeyCode.Alpha3)) {
SetStrategy(new MagicStrategy());
}
if (Input.GetMouseButtonDown(0)) {
RaycastHit hit;
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit)) {
Attack(hit.collider.gameObject);
}
}
}
}
- 按数字键动态切换策略;
- 在
Attack方法中,完全委托给当前策略执行。
四、用法
4.1 ScriptableObject 驱动策略
将策略实现为可编辑资产,便于策划调参与热更新:
public abstract class AttackStrategySO : ScriptableObject, IAttackStrategy {
public abstract void Execute(Transform caster, GameObject target);
}
[CreateAssetMenu("Strategy/Melee")]
public class MeleeStrategySO : AttackStrategySO {
public float damage = 10f;
public float range = 2f;
public override void Execute(Transform caster, GameObject target) {
// 同上
}
}
class Player : MonoBehaviour {
public AttackStrategySO initialStrategy;
private IAttackStrategy _strategy;
void Start() {
_strategy = initialStrategy;
}
public void SetStrategy(AttackStrategySO strategySO) {
_strategy = strategySO;
}
// Attack 与 Update 同上...
}
4.2 策略工厂 & DI
结合抽象工厂与 Zenject,将策略注册到容器,按需注入:
public class GameInstaller : MonoInstaller {
public AttackStrategySO meleeSO;
public AttackStrategySO rangedSO;
public override void InstallBindings() {
Container.Bind<IAttackStrategy>().WithId("Melee").FromInstance(meleeSO).AsTransient();
Container.Bind<IAttackStrategy>().WithId("Ranged").FromInstance(rangedSO).AsTransient();
}
}
public class Player : MonoBehaviour {
[Inject(Id = "Melee")] IAttackStrategy _defaultStrategy;
private IAttackStrategy _strategy;
void Start() {
_strategy = _defaultStrategy;
}
// ...
}
4.3 策略组合(Decorator)
可在现有策略上叠加额外功能,如暴击、伤害加成:
public class CriticalDecorator : IAttackStrategy {
private IAttackStrategy _inner;
private float _critChance;
public CriticalDecorator(IAttackStrategy inner, float critChance) {
_inner = inner; _critChance = critChance;
}
public void Execute(Transform caster, GameObject target) {
if (Random.value < _critChance) {
// 增强逻辑
}
_inner.Execute(caster, target);
}
}
五、性能与调试
GC 控制
- 避免在
Update()中频繁new策略实例,使用 ScriptableObject 或单例策略;
- 避免在
Profiler
- 确认策略切换、执行不带来帧率突降;
可视化
- 在 Inspector 中显示当前策略名称,便于调试:
[ReadOnly] public string currentStrategy; void Update() { currentStrategy = _strategy.GetType().Name; }
六、注意事项
| 场景 | 陷阱 | 建议 |
|---|---|---|
| 频繁实例化策略 | 每帧创建新实例造成 GC 压力 | 对无状态策略使用单例或 ScriptableObject |
| 策略依赖外部资源 | 策略内部直接 Instantiate、Find,导致难以测试 |
将依赖注入到策略构造或 SO 资产,通过工厂模式注入 |
| 过度抽象 | 每个微小差异都写策略,策略类爆炸 | 把真正可变部分提炼到策略,公共逻辑可放在组合/基类中 |
| 策略切换时机不明确 | 在渲染或物理回调中切换,可能导致逻辑不一致 | 在统一 Update 或事件回调时切换,保证执行顺序可控 |
七、小结
- 职责分离:将可变算法或行为封装到
IAttackStrategy、IMoveStrategy等策略中; - 接口依赖:客户端只依赖策略接口,易 Mock、易扩展;
- 资源驱动:建议使用 ScriptableObject 策略,便于无代码热更新与调参;
- 策略复用:对无内部状态策略使用单例,避免过度分配;
- 组合增强:结合装饰器模式,为基础策略动态添加功能(Buff、Debuff、日志等)。