You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

OOP架构疑问:实体动作逻辑的类归属与调用最佳实践

AI结构接口设计的最佳实践方案

我在搭建AI结构时,接口设计上遇到了问题,相关代码如下(注释较为简略):

//Probability是我自定义的类,用于表示概率
public interface IAction {
    /// <summary>
    /// 返回IEntity参数的可能未来状态及其预估概率的Dictionary
    /// </summary>
    Dictionary<IEntity, Probability> EstimatePossibleOutcomeSpectrum(IEntity entity);
    /// <summary>
    /// 让IEntity参数执行此动作
    /// </summary>
    void Do(IEntity entity);
}
public interface IEntity {
}

按常理应为实体(Entity)执行动作(Action),而非动作作用于实体。我可重命名方法优化语法,但希望将Do方法移至IEntity中。不过实现接口时,我不知如何让实体执行动作,只能通过类似以下的转发调用方式:

public class EntityExample {
    /// <summary>
    /// 返回执行IAction参数后的预估未来状态Dictionary
    /// </summary>
    Dictionary<IEntity, Probability> EstimatePossibleOutcomeSpectrum(IAction action) {
        action.[Some method or a Action<Entity> call](this);
    }
    /// <summary>
    /// 执行传入的Action参数
    /// </summary>
    void Do(IAction action) {
        action.[Some method or a Action<Entity> call](this);
    }
}

这种方式感觉和原方案一样甚至更糟。虽然初始方案功能正常,但我认为这近乎不良写法。请问此类场景下的最佳编码实践是什么?

首先得说,你能注意到“实体执行动作”这个语义上的合理性,已经比很多只追求功能实现的开发者想得深了——确实,从领域建模的角度,动作(Action)更应该是实体(Entity)可以选择的行为,而不是反过来让动作“操作”实体。

先拆解下你目前遇到的核心矛盾:

  • 原方案里IAction.Do(IEntity)的语义是“动作作用于实体”,不符合领域直觉
  • 想把Do移到IEntity里,但又陷入了转发调用的尴尬,本质上还是把逻辑丢回给了IAction,没解决问题

下面给你几种适配不同场景的最佳实践方向:

1. 双重分派(Double Dispatch)——语义与灵活性兼顾

这是解决这类“跨接口交互”问题的经典方案,既能保留IEntity执行动作的语义,又能让不同的IAction实现各自的逻辑,完美平衡语义正确性和扩展性。

调整后的接口设计

// 自定义概率类(你已实现,这里仅示意)
public class Probability { /* ... */ }

// 动作接口,新增Accept方法支持实体访问
public interface IAction {
    Dictionary<IEntity, Probability> EstimatePossibleOutcomeSpectrum(IEntity entity);
    // 让实体可以"访问"动作,触发双重分派
    void Accept(IEntity entity);
}

// 实体接口,定义执行动作的方法和动作适配入口
public interface IEntity {
    void Do(IAction action);
    Dictionary<IEntity, Probability> EstimateOutcome(IAction action);
    
    // 为每个具体动作提供重载(双重分派的核心)
    void ExecuteAction(AttackAction action);
    void ExecuteAction(MoveAction action);
    Dictionary<IEntity, Probability> EstimateAttackOutcome(AttackAction action);
    Dictionary<IEntity, Probability> EstimateMoveOutcome(MoveAction action);
}

具体实体实现示例

public class EnemyEntity : IEntity {
    public int Health { get; set; }
    public int Position { get; set; }

    public void Do(IAction action) {
        // 让动作反过来调用实体的适配方法,完成语义反转
        action.Accept(this);
    }

    public Dictionary<IEntity, Probability> EstimateOutcome(IAction action) {
        return action.EstimatePossibleOutcomeSpectrum(this);
    }

    // 具体动作的执行逻辑,完全属于实体职责
    public void ExecuteAction(AttackAction action) {
        // 敌人执行攻击的具体逻辑
        Console.WriteLine($"Enemy attacks for {action.Damage} damage!");
    }

    public void ExecuteAction(MoveAction action) {
        // 敌人移动的具体逻辑
        Position += action.Distance;
        Console.WriteLine($"Enemy moves to position {Position}");
    }

    // 具体动作的预估逻辑
    public Dictionary<IEntity, Probability> EstimateAttackOutcome(AttackAction action) {
        // 模拟攻击后的状态预估
        var result = new Dictionary<IEntity, Probability>();
        var damagedSelf = new EnemyEntity { Health = Health - 5, Position = Position };
        result.Add(damagedSelf, new Probability { Value = 0.8 });
        return result;
    }

    public Dictionary<IEntity, Probability> EstimateMoveOutcome(MoveAction action) {
        var result = new Dictionary<IEntity, Probability>();
        var movedSelf = new EnemyEntity { Health = Health, Position = Position + action.Distance };
        result.Add(movedSelf, new Probability { Value = 1.0 });
        return result;
    }
}

具体动作实现示例

public class AttackAction : IAction {
    public int Damage { get; set; } = 10;

    public Dictionary<IEntity, Probability> EstimatePossibleOutcomeSpectrum(IEntity entity) {
        if (entity is EnemyEntity enemy) {
            return enemy.EstimateAttackOutcome(this);
        }
        throw new NotSupportedException("AttackAction only supports EnemyEntity");
    }

    public void Accept(IEntity entity) {
        if (entity is EnemyEntity enemy) {
            enemy.ExecuteAction(this);
        } else {
            throw new NotSupportedException("AttackAction only supports EnemyEntity");
        }
    }
}

public class MoveAction : IAction {
    public int Distance { get; set; } = 5;

    public Dictionary<IEntity, Probability> EstimatePossibleOutcomeSpectrum(IEntity entity) {
        if (entity is EnemyEntity enemy) {
            return enemy.EstimateMoveOutcome(this);
        }
        throw new NotSupportedException("MoveAction only supports EnemyEntity");
    }

    public void Accept(IEntity entity) {
        if (entity is EnemyEntity enemy) {
            enemy.ExecuteAction(this);
        } else {
            throw new NotSupportedException("MoveAction only supports EnemyEntity");
        }
    }
}

这种方式的核心优势:

  • 语义完全符合直觉:var enemy = new EnemyEntity(); enemy.Do(new AttackAction());
  • 实体和动作的职责清晰:实体负责“执行动作的逻辑”,动作负责“定义动作的属性/规则”
  • 扩展性强:新增动作时只需新增IAction实现,以及在实体中添加对应的适配方法,无需修改原有接口逻辑

2. 将动作作为实体固有方法——极简设计(适合动作固定场景)

如果你的AI系统中动作类型很少,且属于实体的固有行为(比如敌人只有攻击、移动两种动作),那可以直接把动作定义为IEntity的方法,完全省去IAction接口:

public interface IEntity {
    Dictionary<IEntity, Probability> EstimateAttackOutcome();
    void Attack();

    Dictionary<IEntity, Probability> EstimateMoveOutcome();
    void Move();
}

public class EnemyEntity : IEntity {
    public int Health { get; set; }
    public int Position { get; set; }

    public void Attack() {
        // 攻击逻辑
    }

    public Dictionary<IEntity, Probability> EstimateAttackOutcome() {
        // 攻击预估逻辑
        return new Dictionary<IEntity, Probability>();
    }

    public void Move() {
        // 移动逻辑
    }

    public Dictionary<IEntity, Probability> EstimateMoveOutcome() {
        // 移动预估逻辑
        return new Dictionary<IEntity, Probability>();
    }
}

这种方式的优点是极其简单、语义最直接,但缺点是违反开闭原则——新增动作时必须修改IEntity接口,适合小型或动作固定的AI系统。

3. 中介者模式——解耦复杂交互(适合多实体多动作场景)

如果你的系统中有大量实体类型和动作类型,且它们的交互逻辑复杂(比如不同实体执行同一动作的逻辑差异极大),可以引入中介者模式来集中管理所有实体与动作的交互逻辑:

核心设计

public interface IActionMediator {
    Dictionary<IEntity, Probability> EstimateOutcome(IEntity entity, IAction action);
    void ExecuteAction(IEntity entity, IAction action);
}

public class ActionMediator : IActionMediator {
    public Dictionary<IEntity, Probability> EstimateOutcome(IEntity entity, IAction action) {
        // 集中处理所有实体+动作的预估逻辑
        switch (entity, action) {
            case (EnemyEntity enemy, AttackAction attack):
                return enemy.EstimateAttackOutcome(attack);
            case (PlayerEntity player, MoveAction move):
                return player.EstimateMoveOutcome(move);
            // 其他组合逻辑
            default:
                throw new NotSupportedException();
        }
    }

    public void ExecuteAction(IEntity entity, IAction action) {
        // 集中处理所有实体+动作的执行逻辑
        switch (entity, action) {
            case (EnemyEntity enemy, AttackAction attack):
                enemy.Attack(attack);
                break;
            case (PlayerEntity player, MoveAction move):
                player.Move(move);
                break;
            // 其他组合逻辑
            default:
                throw new NotSupportedException();
        }
    }
}

// 简化后的实体接口
public interface IEntity {
    void Do(IAction action, IActionMediator mediator);
    Dictionary<IEntity, Probability> EstimateOutcome(IAction action, IActionMediator mediator);
}

// 实体实现
public class EnemyEntity : IEntity {
    public void Do(IAction action, IActionMediator mediator) {
        mediator.ExecuteAction(this, action);
    }

    public Dictionary<IEntity, Probability> EstimateOutcome(IAction action, IActionMediator mediator) {
        return mediator.EstimateOutcome(this, action);
    }

    // 内部动作逻辑,仅对中介者开放
    internal void Attack(AttackAction action) { /* ... */ }
    internal Dictionary<IEntity, Probability> EstimateAttackOutcome(AttackAction action) { /* ... */ }
}

这种方式的优势是完全解耦实体与动作的依赖,所有交互逻辑集中在中介者中,便于维护复杂的交互规则,但会增加一层抽象,适合大型、复杂的AI系统。


最终选择建议

  • 如果追求语义正确+灵活性强:双重分派是首选方案,兼顾了领域建模和代码扩展性
  • 如果动作类型固定、系统简单:直接将动作作为实体方法最简洁
  • 如果实体和动作种类繁多、交互复杂:中介者模式能更好地管理依赖

内容的提问来源于stack exchange,提问作者Shefeto

火山引擎 最新活动