如何在贫血领域模型设计中使用多态性?

How to use polymorphism in anemic domain model design?

现在我正在开发一个 mmorpg 服务器,让我们来谈谈玩家从物品栏中掉落东西到世界的一个场景

如果我用丰富的域模型设计 Drop,我会创建这样的代码

class Player {
    void Drop(IDropable dropable,vector3 pos){
        RemoveFromInventory();
        dropable.BeDroped(pos);
        Net.Broadcast(pos);
    }
}

如你所见,dropable 将被称为 BeDroped(pos),每个实现 IDropable 的实体都会做一些特殊的事情,例如,放下武器,它会击中某人;放下魔法球,玩家的健康点将减少...

但是如果我想通过贫血域模型进行设计,我如何为每个 IDropable 使用多态性?这是示例,我可以实现的方法是使用 if else 或 switch ,但我知道这是愚蠢的。

class DropService {
    void RemoveFromPlayerInventory(Player player){
        //.....
    }
    void Drop(Player player, IDropable dropable,vector3 pos){
        RemoveFromPlayerInventory(player);
        if(dropable is Weapon)
            OnDropWeapon(dropable,pos);
        else if(dropable is magicball)
            OnDropMagicBall(dropable,pos);
        //.....
        Net.Broadcast(pos);
    }
}

摆脱那些 if/else 链的一种方法:双重分派

示例:

class DropService:IDropService {
    void RemoveFromPlayerInventory(Player player){
        //.....
    }
    void Drop(Player player, IDropable dropable,vector3 pos){
        RemoveFromPlayerInventory(player);
        dropable.BeDropped(this, pos);
        //.....
        Net.Broadcast(pos);
    }

    void IDropService.Drop(Weapon item, Vector3 pos){ /*impl.*/ }
    void IDropService.Drop(Magicball item, Vector3 pos){ /*impl.*/ }
}

public interface IDropable
{
    void Bedropped(IDropService dropper, Vector3 pos);
}

public interface IDropService
{
    void Drop(Weapon item, Vector3 pos);
    void Drop(Magicball item, Vector3 pos);
}

public class Weapon: IDropable
{
    public override void BeDropped(IDropService dropper, Vector3 pos)
    {
        dropper.Drop(this, pos); // Will automagically call the right overload
    }
}

// Same for Magicball

如您所见,没有更多 if/else 或开关。

缺点:人们认为是代码味道的是 DropService 现在需要知道 IDropable 的 every 实现。因此,任何时候新的 class 实现 IDropable,您还需要更改 DropService。

为避免这种情况,您可以考虑多种可能的模式。像几个工厂模式(例如创建适当的 DropBehavior 的工厂?),(Drop-)策略模式 ...

一种方法可能是将游戏对象更多地视为 database-entries 而不是实际的 .net 类型。因此,例如,您可以在编辑器中创建一个游戏对象,为其分配一些图形,以及应该在某个事件上发生的一种或多种效果。所以你的对象可能看起来像

private EffectManager effectManager;
private Dictionary<EventId, EffectId> effectsOnEvents= new ();
public void OnEvent(EventId eventId) {
    if(effectsOnEvents.TryGet(eventId, out var effectId){
        effectManager(this, effectId);
    }
}

所以武器会在 Drop 事件上附加 DropWeapon 效果,魔法球会附加 DropMagicBall 效果,依此类推。有些 class 需要定义每个效果的实际作用,但效果可能比游戏对象少得多。现在您的播放器只需要调用 item.OnEvent(EventId.Drop),这将触发任何关联的行为。

例如,您还可以添加对多个脚本的支持,以允许像魔法球这样的武器既能造成伤害又能治疗。并向脚本添加参数,这样您就可以通过更新数据库中的值轻松更改造成的伤害量或恢复的生命值。

我强烈推荐 Eric Lippers 在 Wizards and warriors 上的文章,他指出使用类型系统对域行为建模可能会出现问题。