首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C#组合-我不相信我完全理解如何实现这个

C#组合-我不相信我完全理解如何实现这个
EN

Stack Overflow用户
提问于 2017-06-21 06:57:24
回答 1查看 5.6K关注 0票数 11

好的,我最近开始学习类,继承,接口,以及它们是如何相互作用的。在此期间,我发现在各种论坛/博客/视频中普遍存在对继承的蔑视和对构图的偏爱。好吧,学点新东西吧。使用这个wiki页面上的例子,我开始四处游玩,试图更好地理解它……到目前为止,我的成就似乎只会让自己更加困惑。

我的代码,然后我会解释我认为是错误的。

MainClass.cs

代码语言:javascript
复制
class MainClass
{
    static void Main(string[] args)
    {
        var player = new Player();
        var enemy = new Enemy();
        var entity = new Entity();

        for(int i = 0; i < GameObject.GameObjects.Count; i++)
        {
            GameObject.GameObjects[i].Type();
            GameObject.GameObjects[i].Draw();
            GameObject.GameObjects[i].Move();
            GameObject.GameObjects[i].Collision();
            GameObject.GameObjects[i].Ai();
            Console.WriteLine();
        }

        Console.ReadKey();
    }
}

GameObject.cs

代码语言:javascript
复制
interface IVisible
{
    void Draw();
}

class Visible : IVisible
{
    public void Draw()
    {
        this.Print("I am visible!");
    }
}

class Invisible : IVisible
{
    public void Draw()
    {
        this.Print("I am invisible!");
    }
}

interface IVelocity
{
    void Move();
}

class Moving : IVelocity
{
    public void Move()
    {
        this.Print("I am moving!");
    }
}

class Stopped : IVelocity
{
    public void Move()
    {
        this.Print("I am stopped!");
    }
}

interface ICollidable
{
    void Collide();
}

class Solid: ICollidable
{
    public void Collide()
    {
        this.Print("I am solid!");
    }
}

class NotSolid: ICollidable
{
    public void Collide()
    {
        this.Print("I am not solid!");
    }
}

interface IAi
{
    void Ai();
}

class Aggressive : IAi
{
    public void Ai()
    {
        this.Print("I am aggressive!");
    }
}

class Passive : IAi
{
    public void Ai()
    {
        this.Print("I am passive!");
    }
}

class GameObject
{
    private readonly IVisible _vis;
    private readonly IVelocity _vel;
    private readonly ICollidable _col;
    private readonly IAi _ai;

    public static List<GameObject> GameObjects = new List<GameObject>();

    public GameObject(IVisible visible, IVelocity velocity)
    {
        _vis = visible;
        _vel = velocity;
        GameObjects.Add(this);
    }

    public GameObject(IVisible visible, IVelocity velocity, ICollidable collision)
    {
        _vis = visible;
        _vel = velocity;
        _col = collision;
        GameObjects.Add(this);
    }

    public GameObject(IVisible visible, IVelocity velocity, ICollidable collision, IAi ai)
    {
        _vis = visible;
        _vel = velocity;
        _col = collision;
        _ai = ai;
        GameObjects.Add(this);
    }

    public void Draw()
    {
        if(_vis != null)
            _vis.Draw();
    }

    public void Move()
    {
        if(_vel != null)
            _vel.Move();
    }

    public void Collision()
    {
        if(_col != null)
            _col.Collide();
    }

    internal void Ai()
    {
        if(_ai != null)
            _ai.Ai();
    }
}

class Player : GameObject
{
    public Player() : base (new Visible(), new Stopped(), new Solid()) { }
}

class Enemy : GameObject
{
    public Enemy() : base(new Visible(), new Stopped(), new Solid(), new Aggressive()) { }
}

class Entity : GameObject
{
    public Entity() : base(new Visible(), new Stopped()) { }
}

Utilities.cs (不用担心这个--我在做这方面的工作时发现了扩展方法,所以也把它们扔了进去)

代码语言:javascript
复制
public static class Utilities
{
    public static void Type(this object o)
    {
        Console.WriteLine(o);
    }

    public static void Print(this object o, string s)
    {
        Console.WriteLine(s);
    }
}

好的,我对这个基本示例所做的更改是将它从始终使用3个接口更改为使用基于类的2-4接口。然而,我立即遇到了如何通过作文来完成这一任务的问题。

我首先尝试为每种类型的GameObject (GameObjectAI()、GameObjectEntity()等)制作不同类型的代码,但这似乎只会导致代码重复和与继承相关的各种不稳定问题--我不知道我在这里是否正确,但创建了一个糟糕的非组合实现。不过,如果是这样的话,我肯定不会理解作文,因为如果不产生这些问题,我就看不出该怎么做。

然后我转向了当前的解决方案。虽然这看起来很不雅致。虽然它产生了预期的输出,但这是不对的--它有诸如Player()或Entity()这样的类,它们获得了它们不使用的接口,然后必须有!= null检查来停止运行时异常,因为它们显然可以调用它们相关联的类。

所以是的,关于作文我觉得我现在还不太清楚。

谢谢!

EN

回答 1

Stack Overflow用户

发布于 2017-06-21 07:06:35

作文?让我们先看看继承是否是正确的工具。这些接口是行为或特征。实现多个行为的最简单方法是简单地从这些接口继承。例如(简化代码):

代码语言:javascript
复制
sealed class Enemy : IMoveable, ICollidable {
    public void Move() { }
    public void Collide() { }
}

这样做的最大缺点是您将有一些代码重复(假设的NetrualFriend类需要重写相同的Enemy逻辑)。

有一些解决方法,最简单的方法是基于这样的假设,即您可能有一些共享某些特性的对象类。通常,如果可以构建一个良好的继承层次结构,则不需要组合。例如,可移动对象也有一个边界框,然后它可以检查碰撞:

代码语言:javascript
复制
abstract class PhysicalObject : IMoveable, ICollidable {
    public virtual void Move() { }
    public virtual void Collide() { }
}

abstract class SentientEntity : PhysicalObject, IAi
{
    public virtual void Ai() { }
}

sealed class Enemy : SentientEntity {
}

sealed class Player : SentientEntity {
}

sealed class Friend : SentientEntity {
}

每个派生类都可能重写基类中定义的默认行为。我会尽量使用继承来描述IS-A关系和描述有-A关系的组合关系(直到语言限制)。单一继承将限制我们或将导致一些代码重复。但是,您可以使用我们的第一个实现,并将作业委托给一个单独的对象,现在是引入组合的时候了。

代码语言:javascript
复制
sealed class Enemy : IMoveable, ICollidable {
    public void Move() => _moveable.Move();

    private readonly IMoveable _moveable = new Moveable();
}

这样,来自Moveable实现的IMoveable代码可以在不同的具体类之间共享和重用。

有时候这还不够。您可以将这两种技术结合起来:

代码语言:javascript
复制
abstract class PhysicalObject : IMoveable, ICollidable {
    protected PhysicalObject() {
        _moveable = CreateMoveableBehavior();
        _collidable = CreateCollidableBehavior();
    }

    public void Move() => _moveable.Move();
    public void Collide() => _collidable.Collide();

    protected virtual IMoveable CreateMoveableBehavior()
        => new Moveable();

    protected virtual ICollidable CreateCollidableBehavior()
        => new Collidable();

    private readonly IMoveable _moveable;
    private readonly ICollidable _collidable;
}

通过这种方式,派生类可以为这些行为提供自己的专门实现。例如,一个鬼魂(假设在我们的游戏中鬼魂是一个物理对象)可能以1/10的概率与其他任何东西发生碰撞:

代码语言:javascript
复制
sealed class Ghost : PhysicalObject {
    protected override CreateCollidableBehavior()
        => new ColliderWithProbability(0.1);
}

如果在游戏引擎中,你需要处理没有物理接触的碰撞(例如,两个带电粒子之间的碰撞)?只需写一个特别的行为,它可以应用在任何地方(例如,处理电磁力和重力)。

现在事情开始变得更有趣了。你可能有一个由多个部分组成的合成物体(例如,由它的身体和翅膀组成的飞机,它可能对武器有不同的抵抗力)。简化示例:

代码语言:javascript
复制
abstract ComposedPhysicalObject : ICollidable {
    public void Collide() {
        Parts.ForEach(part => part.Collide());
    }

    public List<ICollidable> Parts { get } = new List<ICollidable>()
}

在本例中,列表实现了ICollidable,然后强制所有部分都具有这种行为。这可能不是真的:

代码语言:javascript
复制
interface IGameObject { }
interface ICollidable : IGameObject { }

abstract ComposedPhysicalObject : ICollidable {
    public void Collide() {
        foreach (var part in Parts.OfType<ICollidable>())
            part.Collide();
    }

    public List<IGameObject> Parts { get } = new List<IGameObject>()
}

然后,组合对象甚至可以覆盖其部件的默认行为或添加/扩展它(集合通常不具有其单独元素的相同属性)。

我们可以多写1000个例子,无论是更复杂还是更简单。一般来说,我建议不要使您的体系结构过于复杂,除非您真的需要它(特别是对于games...abstractions来说,性能上是有代价的)。您正确地使用测试验证了您的体系结构,IMO是TDD最重要的部分:如果编写测试时,您看到代码比应该的要复杂,那么您需要更改您的体系结构;如果您首先编写测试和体系结构,那么它将更加面向域,并且易于理解。好的,这是理想的case...our语言,有时会迫使我们选择一个解决方案,而不是另一个(例如,在C#中,我们没有多重继承.)

我认为(但这只是我的观点),如果没有一个真正需要的用例,你就不应该“学习作文”。组合和继承只是您选择的工具(如模式BTW),您选择建模您的域,您可能需要首先了解何时在“正确”的情况下使用它们,否则您将(Ab)在将来使用它们,因为它们的效果而不是它们的含义。

在你的example...why中它是次优的吗?因为您试图向基类中添加一些行为(移动、碰撞等),而这些行为可能不适用,并且没有重用任何代码。您正在创建一个行为组合,这些行为可能实现也可能没有实现(这就是为什么在调用这些方法之前检查null的原因,它可能简化为fieldName?.MethodName())。您在运行时解决一些在编译时非常熟悉的问题,编译时检查(几乎)总是更好。

如果每个对象都必须实现该逻辑(我宁愿避免这种逻辑)来简化调用点,那么您就不需要那么复杂了:

代码语言:javascript
复制
abstract class GameObject {
    public virtual void Move() { }
    public virtual void Collide() { }
}

sealed class Player : GameObject {
    public override void Move()
        => _collisionLogic.Move();

    private readonly CollisionLogic _collisionLogic = new CollisionLogic(this);
}

一般来说,组合和继承并不排斥彼此,它们在一起使用时发挥得最好。

票数 11
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/44668852

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档