gpt4 book ai didi

C#合成-我不确信我完全了解如何实现此目标

转载 作者:太空狗 更新时间:2023-10-29 17:51:35 26 4
gpt4 key购买 nike

好的,最近我开始关注类,继承,接口(interface)以及它们之间如何交互。在此期间,我在各种论坛/博客/视频上发现了对继承的普遍不屑一顾,并且青睐作曲。好吧,酷一些新东西要学习。通过使用this wiki page上的示例,我着手尝试并更好地理解它……到目前为止,我的成就似乎只会使自己更加困惑。

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

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个接口(interface)更改为使用2-4个示例。但是,我立即遇到了如何通过合成来做到这一点的问题。

我首先尝试为每种类型制作不同类型的GameObject(GameObjectAI(),GameObjectEntity()等),但这似乎会导致代码重复以及与继承相关的各种奇怪问题-我不知道在正确的轨道上,但创建了不良的非组合实现。尽管是这种情况,但我绝对不了解合成方面的知识,因为我不明白如何在不产生这些问题的情况下做到这一点。

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

是的,关于构图,我觉得我现在还不了解。

谢谢!

最佳答案

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

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关系。单一继承会限制我们或导致某些代码重复。但是,您可以诉诸我们的第一个实现并将任务委派给一个单独的对象,是时候介绍一下组合了:
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>()
}

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

我们可能会再写1,000个更复杂或更简单的示例。总的来说,我建议不要让您的架构过于复杂,除非您确实需要它(尤其是对于游戏...抽象在性能上有代价)。您通过测试正确验证了体系结构,这是正确的,IMO是 TDD 的最重要部分:如果编写测试,您发现代码比应该的复杂,那么您就需要更改体系结构;如果您先编写测试,然后再遵循体系结构,那么它将更加面向领域并且易于理解。好的,那是一个理想的情况...我们选择的语言有时会迫使我们选择一种解决方案,而不是另一种(例如,在C#中,我们没有多重继承...)

我认为(但这只是我的观点)您应该在没有真正需要的用例的情况下,不应该“学习合成”。组合和继承只是您用来为 模型域建模的工具(如模式BTW),您可能需要首先了解何时在“正确”的情况下使用它们,否则由于其影响您将来会(滥用)它们而不是他们的意思。

在您的示例中...为什么它不是次优的?因为您正在尝试在基类中添加某些行为(移动,碰撞等),而这些行为可能不适用并且您没有在重用任何代码,所以您会尝试这样做。您正在创建可能无法实现的行为组合(这就是为什么在调用这些方法之前先检查 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);
}

通常,组合和继承不会相互排斥,并且一起使用时它们发挥最佳作用。

关于C#合成-我不确信我完全了解如何实现此目标,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44668852/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com