views:

81

answers:

3

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--;
    }
}
+1  A: 

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.

Android Eve
I'm not trying to pass the derived class it's own base class. I'm trying to tell the base *weapon* class which *projectile* to create. I'll edit the question.
Tesserex
+2  A: 

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?

BioBuckyBall
Ok, your first suggestion sounds like the class passing I was talking about. For the question you have - It's because the AI makes the decision about when to fire. So it says `Tank.Weapon.Shoot()`. If you have a better idea, I'm open to suggestions. I know my solution isn't perfect.
Tesserex
+2  A: 

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.

CodexArcanum
I think you misunderstood my question. I'm not a new to DI and polymorphism. I already have my code set up like you suggest, almost verbatim. My tank constructor looks like the code you gave. The only difference is I'm using abstract classes instead of interfaces. I'll edit the question to clarify.
Tesserex