views:

442

answers:

4

When should I continue to make derived classes, and when should I just add conditionals to my code? eg for a missile

class Object;
class Projectile : public Object;

class Missile : public Projectile;
class MissileGuided : public Missile;

Or should I implement that last one in the missile's code?

void Missile::Update()
{
    if(homing && ObjectExists(Target))
        TurnTowards(Target.Pos)

    Pos += Motion;
}

I'm thinking that for all the finer details the second one is better, because you start getting combinations of things (eg some missiles may not show on the radar, some may be destroyable, some may acquire new targets if the original is destroyed or out of range, etc)

However then the same could be said for regular projectiles sharing properties of missiles in some cases (eg may be destroyable, large projectiles may show on radar, etc)

And then further I could say that projectiles share properties with ships (both move, on collision they do damage, may show on radar, may be destroyable...)

And then everything ends up back as like 3 classes:

class Entity;
class Object : public Entity;
class Effect : public Entity;

Where is a good point to draw the line between creating derived classes, and implementing the features in the method with flags or something?

+9  A: 

You may want to consider using strategy pattern instead of both approaches and encapsulate behaviours within external classes. Then the behaviours can be injected into the Missile class to make it GuidedMissile or SpaceRocket or whatever else you need.

This way excessive branching of logic within the Missile class could be avoided and you would not need to go into logical complexities associated with the deep inheritance.

Wikipedia has a collection of samples of the pattern usage in several languages: http://en.wikipedia.org/wiki/Strategy_pattern

interface INavigable {
  Course calcCourse (Position current, Destination dest);
}


Class GeoStationaryRocketCPU implements INavigable {
  Course calcCourse (Position current, Destination dest) {
     return dest.getShortestRouteTo (current).getCourse();
  };

}

Class GuidedMissileCPU implements INavigable {
  Course calcCourse (Position current, Destination dest) {
      return dest.getLowestAltRouteTo (current).getCourse();
  };

}



class Missile {
  INavigable CPU;

  void setCPU (INavigable CPU) {
     this.CPU = CPU;
  }

  void fly ()
  {

     ...
     course = this.CPU.calcCourse (pos, dest);
     ...
  }

}


As suggested by another collegue, you could consider using Decorator pattern as well. Just to highlight couple of design issues that might be important in your context if you were to take that route:

  1. To replace just a certain aspect of object's behaviour you'd need to decorate the entire class.
  2. If the public interface of the decorated class (let say missile) to be ever changed (even some functionality unrelated to its navigation aspect) you would have to change all of the decorating classes (i.e. GuidedMissile, Rocket, FireworkRocket etc) to pass on the interface.
  3. In Decorator you would expect that a decorator would add functionality, not replace it. It works by adding invoking its functions then calling the base class method. In the scenario above this would lead to being able to have a GuidedMissileCPU that decorates a GeoStationaryRocketCPU -- clearly this is not what you want. You want it to choose a different navigation strategy not layer navigation strategies on top of one another.

Nonetheless, when provided with a ready-made immutable implementation of Missile decoration could be the key to "unlocking" it and implementing the strategy pattern, to provide the Missile with all sorts of required behaviours.

Totophil
+1  A: 

Inheritance depends on the language/technology you are using, for example in C++ you can derive a class from multiple classes but such construct is not available in Java, C#, PHP etc. but instead you have abstract classes and interfaces, what I am trying to say is that if you have a class for example your company's software will use multiple database management systems and its up to you to design an independent set of classes for such a construct, and you can do it by using an abstract class and implement polymorphism to your set for example:

    public abstract class Database
    {
         public virtual void Connect();
         public virtual int GetNumberOfTables();

    }

and then you can derive a class for specific SQL Server or Oracle implementation

    public class SQLServerDatabase : Database
    {
           public override void Connect()
           {
                   // SQL Implementation
           }

           public override int GetNumberOfTables()
           {
                 // SQL Implementation
           }  

    }


    public class OracleDatabase : Database
    {
           public override void Connect()
           {
                  // Oracle Implementation
           }

           public override int GetNumberOfTables()
           {
                 // Oracle Implementation
           }  
    }

In such a construct you can use inheritance, but avoiding the class logic complexity as I saw in your example above.

As for conditionals you can use to test if you destroyed the object or garbage collector set the object in the finalization queue that it is not available to you any longer.

milot
+1  A: 

I think that in this case you should the Decorator Pattern. Every new functionality you add is a decoration on the original Missile class. For example you could create a new decorated Missile class that can be Guided (GuidedMissile) and that can go underwater (UnderwaterMissile) simply decorating tha Missile class. The good is that the Decorator Pattern works at runtime and you don't need to build all that classes with static behaviour but you can compose the needed behaviour at runtime. Have a look here: http://en.wikipedia.org/wiki/Decorator_pattern http://www.dofactory.com/Patterns/PatternDecorator.aspx

ema
A: 

Just ask yourself, how it would be easier to:

  1. read and understand the code (the small project part and the whole project)
  2. change the code in the future (be very careful when predicting future needs, usually it's better not to adapt your code for "may-be" changes in advance)

Also there are some really rare cases when class hierarchy can seriously affect the performance.

So make derived classes when it's better for above criteria.

Sergey