【问题标题】:C#: Inheritance, Overriding, and HidingC#:继承、覆盖和隐藏
【发布时间】:2011-02-05 12:52:27
【问题描述】:

我在为我的 C# XNA 游戏做出架构决策时遇到了困难。

世界中的基本实体,例如一棵树、僵尸或玩家,被表示为一个游戏对象。每个 GameObject 至少由 GameObjectControllerGameObjectModelGameObjectView 组成。

这三个对于简单的实体来说已经足够了,比如无生命的树木或岩石。然而,当我试图尽可能地保留功能时,继承开始变得笨拙。从语法上讲,我什至不确定如何最好地实现我的目标。

这里是GameObjectController

public class GameObjectController
{
    protected GameObjectModel model;

    protected GameObjectView view;

    public GameObjectController(GameObjectManager gameObjectManager)
    {
        this.gameObjectManager = gameObjectManager;
        model = new GameObjectModel(this);
        view = new GameObjectView(this);
    }

    public GameObjectManager GameObjectManager
    {
        get
        {
            return gameObjectManager;
        }
    }

    public virtual GameObjectView View
    {
        get
        {
            return view;
        }
    }

    public virtual GameObjectModel Model
    {
        get
        {
            return model;
        }
    }

    public virtual void Update(long tick) 
    {
    }
}

我想指定GameObjectController 的每个子类至少可以访问GameObjectViewGameObjectModel。如果子类可以使用这些类,但可能会覆盖更复杂的Update() 方法,我不希望它们必须复制代码来生成这些依赖项。因此,GameObjectController 构造函数设置了这些对象。

但是,有些对象确实想要覆盖模型和视图。这就是问题所在。

有些物体需要战斗,所以是CombatantGameObjects

public class CombatantGameObject : GameObjectController
{
    protected new readonly CombatantGameModel model;
    public new virtual CombatantGameModel Model
    {
        get { return model; }
    }

    protected readonly CombatEngine combatEngine;

    public CombatantGameObject(GameObjectManager gameObjectManager, CombatEngine combatEngine)
        : base(gameObjectManager)
    {
        model = new CombatantGameModel(this);
        this.combatEngine = combatEngine;
    }

    public override void Update(long tick)
    {
        if (model.Health <= 0)
        {
            gameObjectManager.RemoveFromWorld(this);
        }
        base.Update(tick);
    }
}

还是很简单的。我使用new 隐藏实例变量是否正确?请注意,我在这里分配了CombatantObjectController.model,即使GameObjectController.Model 已经设置。而且,战斗人员不需要任何特殊的视图功能,所以他们不理会GameObjectController.View

然后我进入PlayerController,在那里发现了一个错误。

public class PlayerController : CombatantGameObject
{
    private readonly IInputReader inputReader;

    private new readonly PlayerModel model;
    public new PlayerModel Model
    {
        get { return model; }
    }

    private float lastInventoryIndexAt;
    private float lastThrowAt;

    public PlayerController(GameObjectManager gameObjectManager, IInputReader inputReader, CombatEngine combatEngine)
        : base(gameObjectManager, combatEngine)
    {
        this.inputReader = inputReader;
        model = new PlayerModel(this);
        Model.Health = Constants.PLAYER_HEALTH;
    }

    public override void Update(long tick)
    {
        if (Model.Health <= 0)
        {
            gameObjectManager.RemoveFromWorld(this);
            for (int i = 0; i < 10; i++)
            {
                Debug.WriteLine("YOU DEAD SON!!!");
            }
            return;
        }

        UpdateFromInput(tick);
        // ....
    }
   }

第一次执行此行时,我得到一个空引用异常:

model.Body.ApplyImpulse(movementImpulse, model.Position);

model.Position 查看model.Body,它为空。

这是一个在游戏对象部署到世界之前初始化游戏对象的函数:

   public void Initialize(GameObjectController controller, IDictionary<string, string> data, WorldState worldState)
    {
        controller.View.read(data);
        controller.View.createSpriteAnimations(data, _assets);

        controller.Model.read(data);

        SetUpPhysics(controller,
         worldState,
         controller.Model.BoundingCircleRadius,
         Single.Parse(data["x"]),
         Single.Parse(data["y"]), bool.Parse(data["isBullet"]));
    }

每个对象都作为GameObjectController 传递。这是否意味着如果对象真的是PlayerControllercontroller.Model 将引用基类的GameObjectModel 而不是PlayerController 的覆盖PlayerObjectModel

回应rh:

这意味着现在对于 PlayerModel p, p.Model 不等价于 ((CombatantGameObject)p).Model,以及 也不等于 ((GameObjectController)p).Model.

这正是我不想要的。我要:

PlayerController p;
p.Model == ((CombatantGameObject)p).Model
p.Model == ((GameObjectController)p).Model

我该怎么做? override?

【问题讨论】:

  • 答案是:更多的僵尸。一个游戏永远不会有太多的僵尸。
  • 谢谢 - 我已经更新了我的回复,提供了几种可以继续的方式。在这种情况下,覆盖不是您的最佳选择,因为您实际上想要返回不同的 type,这会为属性提供不同的签名。

标签: c# oop architecture inheritance syntax


【解决方案1】:

关键在于你在这里使用了'new'关键字:

private new readonly PlayerModel model;
public new PlayerModel Model
{
    get { return model; }
}

这里:

protected new readonly CombatantGameModel model;
public new virtual CombatantGameModel Model
{
    get { return model; }
}

您的意思是:“我知道我的基类已经定义了这些,但我想定义不同的恰好具有相同名称的。”

这意味着现在对于 PlayerModel p,p.Model 等价于 ((CombatantGameObject)p).Model,并且也 等价于 ((GameObjectController )p).模型。

您可以通过以下几种方式继续操作。

1) 不要提供强类型子属性。 我知道这可能一开始听起来很糟糕,但它实际上是一个非常强大的概念。如果您的基础模型定义了适用于所有子类的适当抽象/虚拟方法,您可以在基础类中定义一次属性并完成。然后子类可以提供自己的实现。

这是一种可能的实现方式。

public class GameObjectController /* ... */
{
    /* ... */
    public GameObjectController()
    {
        Model = new GameObjectModel(this);
    }

    public GameObjectModel Model { get; protected set; }
}

public class CombatantGameObject : GameObjectController
{
    /* ... */
    public CombatantGameObject()
    {
        Model = new CombatantModel(this);
    }
}
/* ... */

2) 通过子类访问时提供强类型属性,但在基类中存储该字段一次。

这可以工作,但要正确执行却很棘手。这不会是我的第一选择。

public class GameObjectController /* ... */
{
    /* ... */
    public GameObjectController()
    {
        Model = new GameObjectModel(this);
    }

    public GameObjectModel Model { get; protected set; }
}

public class CombatantGameObject : GameObjectController
{
    /* ... */
    public CombatantGameObject()
    {
        Model = new CombatantModel(this);
    }

    public new CombatantModel Model
    {
        get
        {
            return (CombatantModel)base.Model;
        }
        protected set
        {
            base.Model = value;
        }
    }
}
/* ... */

此外,请注意不要过早地过度使用复杂的对象模型。有时,从简单开始并积极重构,是获得反映代码实际最终工作方式的良好模型的最佳方式。

【讨论】:

    猜你喜欢
    • 2017-09-21
    • 2012-07-28
    • 2011-01-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-13
    • 1970-01-01
    相关资源
    最近更新 更多