如何依赖注入类/类型?

发布于 2024-09-16 11:21:14 字数 2557 浏览 4 评论 0原文

我正在努力解决一个设计问题,我不希望我的代码因为糟糕的解决方案而变得一团糟。我不会给出一个糟糕的类比,而是会解释我的确切案例。

我正在尝试编写 Wii Play Tanks 的克隆版,但在设计 Tank 类时遇到了麻烦。 Tank 本身是唯一的此类,它的各个部分使用依赖注入。现在的两个部分是 TankAITankWeapon。人工智能处理有关移动和射击的决策,武器描述武器的行为方式 - 发射什么射弹以及发射频率等。我有一个工厂类,可以以不同的组合建造坦克。

我的射弹类是在抽象 Projectile 类下设置的。每个子类都描述射弹的模型、弹跳次数、速度等。

我遇到的问题是每个 TankWeapon 子类在构建新射弹的区域周围复制大量代码,因为他们各自构建了一个不同的类。我想将此代码移至基类中,但我必须以某种方式注入武器需要构造的射弹类本身。我知道我确实可以在构建时将一个类传递给基地,但这感觉是错误的方法。

当我们这样做时,我还有另一个设计问题:如何让我的 AI 类也了解射弹类?他们的决定将取决于发射的射弹的特性,例如它们可以从墙壁反弹多少次。 AI 和 Weapon 类在注入时都会获得对父 Tank 的引用。

编辑:

看来我原来的问题有点令人困惑,所以我将发布代码。我已经为我的坦克设置了 DI。

public class Tank : ISolidObject
{
    public TankAI AISystem { get; private set; }
    public TankWeapon Weapon { get; private set; }

    public Tank(TankAI aiSystem, TankWeapon weapon)
    {
        this.AISystem = aiSystem;
        this.AISystem.Tank = this;

        this.Weapon = weapon;
        this.Weapon.Tank = this;
    }
}

public abstract class TankAI
{
    public Tank Tank { get; set; }

    public abstract void Think();
}

// TankAI implementations aren't important here

public abstract class TankWeapon
{
    protected int maxShotsOnScreen, shotsOnScreen;

    public Tank Tank { get; set; }

    public virtual void Shoot()
    {
        shotsOnScreen++;

        // I really want to put the projectile construction code in here
    }
}

public class BulletWeapon : TankWeapon
{
    public BulletWeapon()
    {
        this.maxShotsOnScreen = 5;
        this.turnSpeed = 1;
    }

    public override void Shoot()
    {
        // here's my problem. Every weapon class duplicates this, because I can't put the projectile construction in the base weapon class.
        if (shotsOnScreen >= maxShotsOnScreen) return;

        base.Shoot();

        // just create it, it will take care of the rest
        double bx = Tank.X - Math.Sin(Tank.AngleTurret * Math.PI / 180.0);
        double by = Tank.Y + Math.Cos(Tank.AngleTurret * Math.PI / 180.0);
        // note that projectiles subscribe themselves to the game entity handler, so  don't have to store it myself.

        // this weapon creates bullets. A different weapon might create rockets. How would the base class know which? Is there any way I can prevent this code from being duplicated?
        new Bullet(bx, by, Tank.AngleTurret).Death += ShotDeath;
    }

    private void ShotDeath(Projectile p)
    {
        p.Death -= ShotDeath;
        shotsOnScreen--;
    }
}

I'm struggling with a design problem and I don't want my code to become a mess because of a bad solution. Rather than give a poor analogy I'll just explain my exact case.

I'm trying to write a clone of Wii Play Tanks, and I'm having trouble designing the Tank classes. Tank itself is the only such class, it uses dependency injection for its parts. The two parts right now are TankAI and TankWeapon. The AI handles decisions about movement and firing, the weapon describes how the weapon behaves - what projectiles it fires, and how often, etc. I have a factory class that builds tanks in different combinations.

My projectile classes are set up under an abstract Projectile class. Each subclass describes the projectile's model, number of bounces, speed, etc.

The problem I'm having is that each TankWeapon subclass is duplicating a lot of code around the area where they construct a new projectile, because they each construct a different class. I want to move this code into the base class, but I would have to somehow inject the projectile class itself that the weapon needs to construct. I know I could literally pass a Class to the base upon construction, but that feels like the wrong way to go.

And while we're at it I have another design problem: How can I make my AI classes aware of the projectile class as well? Their decisions will depend on properties of the projectile being fired, such as how many times they can bounce off walls. Both the AI and Weapon classes are being given a reference to the parent Tank upon injection.

Edit:

It seems like my original question was a bit confusing, so I'll post code. I already have the DI set up for my tank.

public class Tank : ISolidObject
{
    public TankAI AISystem { get; private set; }
    public TankWeapon Weapon { get; private set; }

    public Tank(TankAI aiSystem, TankWeapon weapon)
    {
        this.AISystem = aiSystem;
        this.AISystem.Tank = this;

        this.Weapon = weapon;
        this.Weapon.Tank = this;
    }
}

public abstract class TankAI
{
    public Tank Tank { get; set; }

    public abstract void Think();
}

// TankAI implementations aren't important here

public abstract class TankWeapon
{
    protected int maxShotsOnScreen, shotsOnScreen;

    public Tank Tank { get; set; }

    public virtual void Shoot()
    {
        shotsOnScreen++;

        // I really want to put the projectile construction code in here
    }
}

public class BulletWeapon : TankWeapon
{
    public BulletWeapon()
    {
        this.maxShotsOnScreen = 5;
        this.turnSpeed = 1;
    }

    public override void Shoot()
    {
        // here's my problem. Every weapon class duplicates this, because I can't put the projectile construction in the base weapon class.
        if (shotsOnScreen >= maxShotsOnScreen) return;

        base.Shoot();

        // just create it, it will take care of the rest
        double bx = Tank.X - Math.Sin(Tank.AngleTurret * Math.PI / 180.0);
        double by = Tank.Y + Math.Cos(Tank.AngleTurret * Math.PI / 180.0);
        // note that projectiles subscribe themselves to the game entity handler, so  don't have to store it myself.

        // this weapon creates bullets. A different weapon might create rockets. How would the base class know which? Is there any way I can prevent this code from being duplicated?
        new Bullet(bx, by, Tank.AngleTurret).Death += ShotDeath;
    }

    private void ShotDeath(Projectile p)
    {
        p.Death -= ShotDeath;
        shotsOnScreen--;
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

青春有你 2024-09-23 11:21:14

对于第一个问题,听起来您需要一个 ProjectileFactory
为什么

// somewhere in tank weapon's Fire method or whatever
Projectile p = projectileFactory.Create( myProjectile.GetType() );

对于第二个问题,人工智能需要为

public Tank( TankAi ai, TankWeapon w) // ...
public TankWeapon( Tank t, Projectile p ) // ...
public TankAi( Tank t, Projectile p ) // ...
public TankAi( Tank t, Type projectileType ) // ...

您注入一个Projectile或一个Type一个问题... 武器和人工智能是否参考了坦克?

For the first question, it sounds like you need a ProjectileFactory
It would look something like

// somewhere in tank weapon's Fire method or whatever
Projectile p = projectileFactory.Create( myProjectile.GetType() );

For the second question, have the AI require injection of a Projectile or a Type

public Tank( TankAi ai, TankWeapon w) // ...
public TankWeapon( Tank t, Projectile p ) // ...
public TankAi( Tank t, Projectile p ) // ...
public TankAi( Tank t, Type projectileType ) // ...

A question for you...Why do the weapon and ai get references to the tank?

意犹 2024-09-23 11:21:14

听起来您没有使用足够的接口。它有助于思考行为(实现)和功能(公开的接口)之间的区别。

您希望每个射弹、AI 和武器以相同的方式运行(具有相同的界面),但实现独特的行为,并具有一些共享的行为。此类的典型模型是具有 IWeapon、IProjectile 和 IIntelligence 接口,它们定义这些对象的公开公共面。然后,您将拥有每个实现该接口的基类(例如 BaseProjectile),并提供一些通用行为供所有 Projectile 使用。

现在,在类的构造函数(或 setter 或任何位置)中,您可以接收该类型的接口。

所以 AI_Tank_Boss 类可能看起来像

public class AI_Tank_Boss : BaseTank

public AI_Tank_Boss(IWeapon weapon, IInteligence ai)
{
    this.Weapon = weapon;
    this.AI = ai;
}

现在每个依赖于 AI 方法的坦克方法(也许是从 AI 发射的事件,坦克会根据这些事件执行某些操作?)可以实现以使用该接口,并且任何武器 -具体代码会调用IWeapon接口。

实际发生的情况取决于特定 Weapon 子类如何实现方法以及它如何使用 BaseWeapon 中的通用代码。这是多态性的基础,也是注入起作用的原因。

It sounds like you aren't using enough Interfaces. It helps to think about the distinction between behavior (implementation) and functionality (the exposed interface).

You want each projectile, AI, and weapon to function the same way (to have the same interface) but to implement unique behavior, with some shared behaviors. A typical model of such would be to have IWeapon, IProjectile, and IIntelligence interfaces which define the exposed public face of those objects. Then you'd have a base class of each (BaseProjectile, for example) that implements the interface, and provides some common behavior for all Projectiles to use.

Now in the constructor (or a setter, or whereever) on your classes, you take in an Interface to the type.

So AI_Tank_Boss class might look like

public class AI_Tank_Boss : BaseTank

public AI_Tank_Boss(IWeapon weapon, IInteligence ai)
{
    this.Weapon = weapon;
    this.AI = ai;
}

Now each of your tank methods that rely on an AI method (perhaps events that fire from the AI and the tank looks to those events to do something?) can be implemented to use the interface, and any weapon-specific code will call the IWeapon interface.

What actually happens is based on how the particular Weapon subclass implements the methods and how it uses the common code in the BaseWeapon. This is the basis of polymorphism and why injection works.

坏尐絯℡ 2024-09-23 11:21:14

在构建时将一个类传递给基地确实是错误的方法。 基类不应该了解其派生类。如果您“必须以某种方式注入武器需要构造的射弹类本身”,则意味着您没有正确设计类层次结构和方法。

除非您在这里发布并举例说明您需要通过的内容,否则我很难提供具体的解决方案。

Passing a class to the base upon construction is indeed the wrong way to go. The base class should have no knowledge of its derived classes. If you "have to somehow inject the projectile class itself that the weapon needs to construct" it means you haven't designed your class hierarchy and methods properly.

Unless you post here and example of what you need to pass, it would be very difficult for me to provide a specific solution.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文