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 handle开发者_JS百科s 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--;
}
}
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?
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.
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.
精彩评论