好的,我最近开始学习类,继承,接口,以及它们是如何相互作用的。在此期间,我发现在各种论坛/博客/视频中普遍存在对继承的蔑视和对构图的偏爱。好吧,学点新东西吧。使用这个wiki页面上的例子,我开始四处游玩,试图更好地理解它……到目前为止,我的成就似乎只会让自己更加困惑。
我的代码,然后我会解释我认为是错误的。
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检查来停止运行时异常,因为它们显然可以调用它们相关联的类。
所以是的,关于作文我觉得我现在还不太清楚。
谢谢!
发布于 2017-06-21 07:06:35
作文?让我们先看看继承是否是正确的工具。这些接口是行为或特征。实现多个行为的最简单方法是简单地从这些接口继承。例如(简化代码):
sealed class Enemy : IMoveable, ICollidable {
public void Move() { }
public void Collide() { }
}这样做的最大缺点是您将有一些代码重复(假设的Netrual和Friend类需要重写相同的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关系和描述有-A关系的组合关系(直到语言限制)。单一继承将限制我们或将导致一些代码重复。但是,您可以使用我们的第一个实现,并将作业委托给一个单独的对象,现在是引入组合的时候了。
sealed class Enemy : IMoveable, ICollidable {
public void Move() => _moveable.Move();
private readonly IMoveable _moveable = new Moveable();
}这样,来自Moveable实现的IMoveable代码可以在不同的具体类之间共享和重用。
有时候这还不够。您可以将这两种技术结合起来:
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个例子,无论是更复杂还是更简单。一般来说,我建议不要使您的体系结构过于复杂,除非您真的需要它(特别是对于games...abstractions来说,性能上是有代价的)。您正确地使用测试验证了您的体系结构,IMO是TDD最重要的部分:如果编写测试时,您看到代码比应该的要复杂,那么您需要更改您的体系结构;如果您首先编写测试和体系结构,那么它将更加面向域,并且易于理解。好的,这是理想的case...our语言,有时会迫使我们选择一个解决方案,而不是另一个(例如,在C#中,我们没有多重继承.)
我认为(但这只是我的观点),如果没有一个真正需要的用例,你就不应该“学习作文”。组合和继承只是您选择的工具(如模式BTW),您选择建模您的域,您可能需要首先了解何时在“正确”的情况下使用它们,否则您将(Ab)在将来使用它们,因为它们的效果而不是它们的含义。
在你的example...why中它是次优的吗?因为您试图向基类中添加一些行为(移动、碰撞等),而这些行为可能不适用,并且没有重用任何代码。您正在创建一个行为组合,这些行为可能实现也可能没有实现(这就是为什么在调用这些方法之前检查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);
}一般来说,组合和继承并不排斥彼此,它们在一起使用时发挥得最好。
https://stackoverflow.com/questions/44668852
复制相似问题