2017-06-21 27 views
8

好的,所以我最近一直在加快类,继承,接口以及它们如何相互交互。在此期间,我发现了对各种论坛/博客/视频的继承和赞成作品的一般声音蔑视。好的,很酷的东西要学习。使用this wiki page上的例子,我开始尝试并更好地理解它......我迄今为止的成就似乎只会让我更加困惑。C#作文 - 我不确信我完全理解如何实现这个

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

MainClass.cs

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

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(会不会担心这一点 - 我发现了扩展方法而努力这也是如此)

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检查来停止运行时异常他们显然可以拥有相关的类。

所以,是的,有一些关于作文我觉得我现在没有得到。

谢谢!

+1

至于一切;使用* moderation *中的模式。您已经正确识别出这非常详细,并且存在许多问题。如果你想要像伐木这样的东西,组成就派上用场了;一个可能由玩家类执行的任务,但不与玩家直接相关*。另一个例子可能是动画师;提供玩家的东西。而不是使用继承:'player:animator'和'monster:animator',你会为玩家和怪物提供一个动画实例。 – Rob

回答

7

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

sealed class Enemy : IMoveable, ICollidable { 
    public void Move() { } 
    public void Collide() { } 
} 

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

有一些变通方法这一点,最简单的一种是基于你可能有类共享一些特性的对象的假设。如果你可以建立一个良好的继承层次结构,你通常不需要组合。例如,一个可移动的对象还具有边界框那么它可以检查碰撞:

在基类定义
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关系和组合来描述HAS-A关系。单一继承将限制我们或将导致一些代码重复。你可以,但是,诉诸我们的第一个执行和委托工作到一个单独的对象,它的时间来介绍组成:从Moveable实施IMoveable

sealed class Enemy : IMoveable, ICollidable { 
    public void Move() => _moveable.Move(); 

    private readonly IMoveable _moveable = new Moveable(); 
} 

这样的代码可以共享之间重用不同的具体类。

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

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的碰撞概率:

sealed class Ghost : PhysicalObject { 
    protected override CreateCollidableBehavior() 
     => new ColliderWithProbability(0.1); 
} 

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

现在事情开始变得更有趣了。你可能有一个由多个部件组成的物体(例如由它的身体和机翼制成的飞机,可能具有对武器的不同抵抗力)组成的物体。简单的例子:

abstract ComposedPhysicalObject : ICollidable { 
    public void Collide() { 
     Parts.ForEach(part => part.Collide()); 
    } 

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

在这种情况下,列表实现ICollidable然后它迫使所有的零件有此行为。这可能不是真的:然后

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个更复杂或更简单的例子。一般来说,我建议不要让你的架构过于复杂,除非你真的需要它(特别是对于游戏...抽象在性能上有价格)。你做了正确的验证你的体系结构测试,IMO是TDD最重要的部分:如果编写你的测试,你会发现代码比它更复杂,那么你需要改变你的架构;如果你第一次写测试和体系结构如下,那么它将更多域为导向并且易于理解。好的,这是理想的情况......我们选择的语言有时会迫使我们选择一种解决方案而不是另一种(例如在C#中,我们没有多重继承...)意见)认为,如果没有真正需要的用例,你不应该“学习构图”。构图和继承只是工具(如模式顺便说一句)你选择模型您的域名,你可能需要先了解何时使用它们在“正确”的情况下,或者你会(未来)使用它们,因为他们的效果而不是他们的意思。

在你的例子中......为什么它不是最理想的?因为你试图在基类不适用时添加一些行为(移动,碰撞等),并且你没有重用任何代码。您创建的行为组合可能会或可能不会实施(这就是为什么您在调用这些方法之前检查null,顺便说一句,它可能简化为fieldName?.MethodName())。您正在运行时解决在编译时已知的问题,并且编译时检查(几乎)总是可取的。

如果每个对象都有实现一个逻辑(我宁愿避免这种情况),以简化您的呼叫点,那么你并不需要所有的复杂性:

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); 
} 

在一般组成和继承并不互相排斥并且在一起使用时它们发挥最好。

+0

感谢您的回应,我一定会通过您的代码进行谜题,并在此再采取一些措施。我已经看到了一些我还没有学习的东西(比如lambda表达式),所以我可能会在构图方面领先于自己。 当你说顺便验证架构,这不是一个项目的测试,这实际上是我试图通过玩代码来学习构图 - 所以它的不必要的冗长都是类似的课程。 – Mofunkles

+3

我认为(但这只是我的观点),如果没有真正需要的用例,你就不应该“学习构图”。组合和继承只是您选择用于建模域的工具,您可能需要首先了解何时在“正确”的情况下使用它们(或者您将(ab)将来使用它们是因为它们的**效果**,而不是他们的**含义**)。 –

+0

有了测试,我并不是指项目的“原型”或“概念验证”,而是**单元测试**。您编写“算法”和代码来运用它。如果一起完成(可以对于TDD来说或多或少是务实的),但对于初学者来说最重要的一点是你会“使用你自己的代码”。如果很难写和/或阅读或者太“黑客”,那么架构可能是错误的你正在使用错误的工具)。 –