我正在努力解决设计问题,我不希望我的代码因为一个糟糕的解决方案而变得混乱。而不是给出一个糟糕的比喻,我只会解释我的确切案例。
我正在尝试编写一个Wii Play Tanks的克隆版,而我在设计Tank类时遇到了麻烦。 Tank
本身是唯一的类,它使用依赖注入的部分。现在这两个部分是TankAI
和TankWeapon
。人工智能会处理有关移动和射击的决定,武器描述武器的行为方式 - 它发射的射弹,射击频率等等。我有一个工厂级别,可以用不同的组合来制造坦克。
我的射弹课程是在抽象的Projectile
课程下设置的。每个子类描述了弹丸的模型,弹跳次数,速度等。
我遇到的问题是每个TankWeapon
子类在构造新射弹的区域周围复制了大量代码,因为它们各自构造了一个不同的类。我想将此代码移动到基类中,但我必须以某种方式注入武器需要构造的抛射类本身。我知道我可以在构造时将一个Class传递给基础,但这感觉就像是错误的方法。
虽然我们遇到了这个问题,但我还有另外一个设计问题:我如何才能让我的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--;
}
}
答案 0 :(得分:2)
对于第一个问题,听起来你需要ProjectileFactory
它看起来像
// somewhere in tank weapon's Fire method or whatever
Projectile p = projectileFactory.Create( myProjectile.GetType() );
对于第二个问题,让AI需要注入Projectile
或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 ) // ...
问题对你来说......为什么武器和ai会引用坦克?
答案 1 :(得分:2)
听起来你没有使用足够的接口。它有助于考虑行为(实现)和功能(暴露的接口)之间的区别。
你希望每个射弹,AI和武器以相同的方式运行(具有相同的界面),但是要实现具有某些共享行为的独特行为。典型的模型是具有IWeapon,IProjectile和IIntelligence接口,这些接口定义了这些对象的公开面。然后你将拥有一个实现接口的基类(例如BaseProjectile),并为所有Projectiles提供一些常见的行为。
现在在类的构造函数(或者setter或者whereever)中,你可以获得一个类型的接口。
所以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中的公共代码。这是多态性的基础,也是注射起作用的原因。
答案 2 :(得分:1)
在施工时将课程传递到基地确实是错误的方法。 基类应该不知道它的派生类。如果你“必须以某种方式注入武器需要构造的射弹类本身”,这意味着你没有正确设计你的类层次结构和方法
除非您在此处发布以及您需要通过的示例,否则我将很难提供具体的解决方案。