• Advertisement
Sign in to follow this  

Having trouble harnessing the power of Inheritance. (best practices advice requested)

This topic is 1045 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

It seems every time I try to use inheritance to save time, it ends up being way more trouble than its worth, and I end up ripping out the inheritance code and doing it without. 

 

Heres a great example. This is C# in case that matters.

 

I'm making a game which has a lot of different types of bad guys. So naturally I thought inheritance. I created a Baddie class which had some very basic stuff, like X,Y position, hitpoints and movement speed. Then I began creating all sorts of specific bad guy classes that inherit from Baddie. I've got Slimer, Knight, Wizard, and Snake bad guys for example, and a lot more, that all extend Baddie.

 

While they all share the basic position, hitpoints and speed, the big problem is they are quite different other than that. The Wizard casts spells, the Snake just moves around randomly, and the Knight charges up his swing and then chops.  I'm finding that I have to have specific code depending on the bad guy type, which if I'm not mistaken defeats the point of using inheritance in the first place!

 

So for example I have instances of all these different bad guy types in a single List<Baddie> list. I'm looping through the list and I want to update each baddie, regardless of the type. However I'm having to use all kinds of type-specific code like this:

foreach (Baddie theBaddie in baddieList) {

if (theBaddie is Knight) {...}

if (theBaddie is Wizard) {...}

}

 

Its becoming quite a mess and I'm wondering why I don't just scrap the whole Inheritance idea, and simply have a separate list for each baddie type:

 

List<Knight>

List<Slimer>

List<Wizard>

...etc

 

And separate update loops for each list.

 

Am I using inheritance wrong? Is this not an appropriate place to use inheritance? I'm starting to think that inheritance is pretty useless unless all of your classes are unbelievably similar. As soon as a class is more than just trivially different, inheritance is not going to help.

 

 

Share this post


Link to post
Share on other sites
Advertisement

Hi.

 

maybe all you need to have is some virtual methods.

 

here or here second post explains it well.

Edited by ankhd

Share this post


Link to post
Share on other sites

Having trouble harnessing the power of Inheritance. (best practices advice requested)


Step 1: Don't.

Am I using inheritance wrong? Is this not an appropriate place to use inheritance? I'm starting to think that inheritance is pretty useless unless all of your classes are unbelievably similar. As soon as a class is more than just trivially different, inheritance is not going to help.


Yup, you got the right of it. Look up the words "aggregation" and "composition." There are plenty of good times to use inheritance, but this isn't one of them.

Its becoming quite a mess and I'm wondering why I don't just scrap the whole Inheritance idea, and simply have a separate list for each baddie type:
<snip>
And separate update loops for each list.


This isn't too far off from how many AAA games work. Many older games did exactly what you outlined while newer ones often use a component-based approach rather than distinct monolithic objects (if for no other reason than because they don't wnat to hardcode their list of enemies in the game and force designers to wait for engineer support to make any simple change).

Share this post


Link to post
Share on other sites

List<Knight>
List<Slimer>
List<Wizard>
...etc
 
And separate update loops for each list.
 
Am I using inheritance wrong? Is this not an appropriate place to use inheritance? I'm starting to think that inheritance is pretty useless unless all of your classes are unbelievably similar. As soon as a class is more than just trivially different, inheritance is not going to help.

The difficulty is that it doesn't extend.

 

Let's say you have a bunch of these monster classes.  Gnome, Soldier, Centaur, Dragon, Angel, Demon, Monkey, Ape...

 

And then you look over it. What kind of difference is there between monkey and ape? So you make a base class and inherit there, too.

 

Then you decide you want some of those to be archers and some to be melee, so you get GnomeArcher, SoldierArcher, ApeArcher, GnomeSwordsman, SoldierSwordsman, DemonSwordsman. Then you decide to make something else, and very soon you're got a combinatorial explosion with thousands of classes.

 

If you use composition you avoid that. You have a character class. It is composed of a creature type (gnome, ape, dragon, demon, whatever), and a set of components. It may have a locomotion component with parameters describing how it moves. It may have an Archery component allowing it to shoot some type of items, perhaps arrows, perhaps lighting bolts, perhaps fireballs. It may have a Melee component allowing it to use an attached weapon.

 

With that system you can build everything from a dragon (flying locomotion, archery with fireball), wizards (character locomotion, archery with low power lighting bolts), soldiers (character locomotion, melee attack with sword) a trebuchet (slow moving locomotion, archery with big stick), archer towers (near-instant rotation only locomotion, fires arrows), cannon towers (slow moving rotation only locomotion, fires cannonballs).  

 

 

 

As for when to use inheritance, its when all the main things work the same and a small number of internals are changing.

 

You normally want an inheritance tree to be shallow and wide.  That is, you start with one base class and have a large number of derived classes, maybe twenty or fifty or even more, depending on your situation.  Usually the classes should only modify internal behavior.

 

Over the years, I've also found the best inheritance trees have no virtual public members. The member functions can call protected and private member functions, but virtual functions shouldn't be called by others. In practice over time a public virtual method will take on additional meaning and violate the IS-A rule. Instead every instance should do the same basic thing and only look up protected member functions to do whatever slightly modified functionality is necessary.

 

There are many common examples of it. This one stolen from Wikipedia's Factory Method example, then modified slightly to C++.

class MazeGame {
    public:

    /* NOT virtual */
    void BuildMaze() {
        Room* room1 = makeRoom();
        Room* room2 = makeRoom();
        room1->connect(room2);
        this->addRoom(room1);
        this->addRoom(room2);
    }
 
    protected:
     virtual Room* makeRoom() {
        return new OrdinaryRoom();
    }
}

class MagicMazeGame : public MazeGame {

    protected:
    virtual Room* makeRoom() {
        return new MagicRoom();
    }
}

If we allowed BuildMaze to be virtual then each derived class could change it to be whatever details they wanted. In practice if BuildMaze were virtual it would start out okay, but over time new features would be added, and MiniGolfMazeGame::BuildMaze() might change some game states, DragonWarfareMazeGame::BuildMaze() will modify some UI elements, and soon you'll find changes to the base game also mean rewriting most of the derived classes as well.

 

-----

 

Note that ECS-based games (Entity Component Systems) tend to use both composition and inheritance.

 

The individual components tend to be derived from a base class, building up whatever behaviors you need.  There may be a large number in a shallow but deep inheritance tree. Perhaps hundreds, perhaps thousands, of component building pieces.  There will be a variety of different locomotion components, respawn components, animation components, event trigger components, and on and on and on.

 

These components are composed together inside a game object. The game object has a collection of components or behaviors, rather than inheriting from them with multiple inheritance or whatever you would have done.

 

-----

 

This doesn't mean that inheritance trees and object interfaces doesn't work as a strategy. It can work, and it has worked for many major games.  However, it tends to be fragile and difficult to extend, it also tends to require more careful and thoughtful design to work successfully. It is easier and less error prone to build through composition.

Share this post


Link to post
Share on other sites

 

Am I using inheritance wrong? Is this not an appropriate place to use inheritance? I'm starting to think that inheritance is pretty useless unless all of your classes are unbelievably similar. As soon as a class is more than just trivially different, inheritance is not going to help.


Yup, you got the right of it. Look up the words "aggregation" and "composition." There are plenty of good times to use inheritance, but this isn't one of them.
 

 

I don't quite understand why you say this. I'm familiar with inheritance vs composition, and IS-A vs HAS-A. It seems to me that this is clearly an IS-A situation. 
Wizard IS-A Baddie

Knight IS-A Baddie

Slimer IS-A Baddie

etc.

 

This is exactly what I was thinking when I decided to use inheritance for this problem. Yet this is apparently not a good time to use inheritance. Can you explain why everyone always says to do the IS-A vs HAS-A test, when it fails in this extremely simple case? I did search the forums before posting and I'd say the number one piece of advice given is to think about IS-A vs HAS-A to determine whether inheritance or composition is more appropriate. Where is my thinking going wrong here? How is my Baddie heirarchy a HAS-A relationship instead of an IS-A ?

 

 

Anyway, everyeone else has posted some great information too. I'll digest it all and post back soon. Thanks!

Share this post


Link to post
Share on other sites

The way I think of it is this: it has the properties of a baddie so therefore it's implied that it is a baddie and can be treated as one.

 

It's basically a form of duck typing, which is an alternate to using interfaces.  Both are forms of polymorphism, but since we want our objects to be easily changed (essentially making them dynamically typed) we use duck typing instead of interfaces and inheritance.  This of course goes along with avoiding problems related to the deadly diamond and virtual function calls.

Edited by Boreal Games

Share this post


Link to post
Share on other sites

I don't quite understand why you say this. I'm familiar with inheritance vs composition, and IS-A vs HAS-A. It seems to me that this is clearly an IS-A situation. 
Wizard IS-A Baddie
Knight IS-A Baddie
Slimer IS-A Baddie

 
The problem is that you use IS-A in the way we normally use the term in natural, spoken language. However in OOP the term has very specific meaning. I personally blame bad teachers for this misunderstanding, as inheritance is often taught with contrived real world examples that never actually occur in real software projects. (Plant-Flower-Tree-Bush for example)
 
Make yourself familiar with the Liskov substitution principle. This should be the benchmark to decide wether you should use inheritance or composition. As a general rule of thumb: When in doubt, use composition.

Share this post


Link to post
Share on other sites

I don't quite understand why you say this. I'm familiar with inheritance vs composition, and IS-A vs HAS-A. It seems to me that this is clearly an IS-A situation. 
Wizard IS-A Baddie
Knight IS-A Baddie
Slimer IS-A Baddie
etc.


You're thinking like a person, not a computer. Your real-world taxonomies don't mean a thing to a mathemetical computation engine crunching through logical structure of algorithms. Programming languages are made to execute code, not to model dictionary definitions of real-world objects.

Why would your program care in any way, shape, or form that some object is a Baddie? What does that possibly mean to your code or your computer? Your program cares about what data that object has and which operations are legal to perform on that data. e.g., is it something that can perform an attack against another game object? Because there's certainly not something that only a Baddie can do. Is it something that can be drawn? That's certainly not specific to a Baddie. Does it have AI? So would a bot controlling a friendly NPC, so it's certainly not limited to a Baddie. And so on; you are very likely to find that almost nothing in your code is in any way specific to a Baddie; on the other hand, you're likely to find a lot of functionality that makes sense on a diverse cross-section of different types of game objects, which means you either need to bloat up all game objects to put that functionality into a single univeral base class (so everything IS-A GameObject) or using composition to give that functionality only to the objects that need it (so a Wizard HAS-A RenderableComponent... but an AmbientSoundSource or ScriptTriggerZone or so on wouldn't).

Trying to express things like "an orange is a fruit" is the first sin of bad object-oriented programming (and it's so depressingly prevalent because every terrible Java/C# school and book starts off with almost that exact bad example...) and has directly led to the poor reputation OOP has unfairly earned. "Wizard is a Baddie" is a squishy-human-brain form of categorization, not something that's relevant to what program actually needs to do.

Use IS-A and inheritance to model functional interfaces and abstractions that you program against, e.g. FmodAudioService IS-A(n) AudioService, and not to model your real-world taxonomies.

Share this post


Link to post
Share on other sites

Thanks all! I've definitely learned that IS-A has a much stricter definition in programming than what I've always thought. 

 

Right now I'm trying to decide whether to push forward with my inheritance code by fixing it, or whether to abandon it and simply have separate unrelated custom classes for each baddie type. OR the third option is to use composition, which I think I still need to read up on. Here's my high level thoughts on the 3 approaches. Comments are much appreciated!

 

Inheritance

 

I could maybe fix my current design by removing all the 

if (theBaddie is Knight) { ... }
if (theBaddie is Wizard) { ... }

tests. I guess in this case I should have something like a Baddie.Update() method that each subclass can override. So the code would look more like this:

theBaddie.Update();

and any Knight or Wizard specific code would be in the overridden Knight.Update() and Wizard.Update() functions respectively. That way the calling code does not need to know the actual type of Baddie object its dealing with. This definitely seems cleaner than what I have so far, however I still have a lot of thinking to do, to see if this approach will work for all of my Baddie classes. For example, only the Wizard can cast spells, so my initial thought is that I need a Wizard.CastSpell() function, that ONLY the Wizard has. But I think this breaks the inheritance model because suddenly the Wizard is special and not exactly a Baddie anymore. So instead of a custom new CastSpell() function in the Wizard class, maybe I just add spell-casting code into the  Wizard.Update() function, thus keeping with the strict IS-A rule.

 

 

Custom unrelated classes

 

So this one seems pretty simple. I simply make each baddie type its own class, that does not inherit from anything. This gives me maximum flexibility because there are no restrictions placed on any of the individual Baddie classes. I can have any variables, functions and behaviors I want, without having to conform to what the Base class defines. The major down side I see with this approach is there will be a ton of similar looking (almost repeated) code. I will need separate loops for each baddie type:

 

// update all baddies

foreach (Wizard baddie in baddieList_Wizards) {

   baddie.update()

}

foreach (Knight baddie in baddieList_Knights) {

   baddie.update()

   if (castTimer <= 0) {

       baddie.CastSpell();

       castTimer = 1000;

   }

}

foreach (Slimer baddie in baddieList_Slimers) {

   baddie.update()

}

foreach (Snake baddie in baddieList_Snakes) {

   baddie.update()

}

 

Composition

 

I think I need to read up some more on this because I dont quite understand. From what I gather, I would have a single barebones Baddie class which can contain almost any combination of behavior objects. Something like this I think:

 

class Baddie {

   private Component_Health cHealth = null;

   private Component_Location cLocation = null;

   private Component_SpellCasting cSpellCasting = null;

   private Component_DeathAction cDeathAction = null;

... etc

}

 

Then I could make a Baddie behave any way I want it to, by just giving it the right components. For example:

 

class Component_Health {

// defines how healthy the baddie is, and what it takes to kill him

public int hitPointsCurrent;

public int hitPointsMaximum;

public int armourCurrent;   // hitpoints are not harmed until armour is reduced to zero

public int armourMaximum;

}

 

class Component_DeathAction {

// defines any special action that occurs when a baddie dies

public int dropGoldChance;

public int dropItemChance;

public int explodeChance;

}

 

 

So a new baddie could be created like this:

Baddie b = new Baddie();

 

Component_Health c = new Component_Health();

c.hitPointsCurrent = 10;

c.hitPointsMaximum = 10;

b.cHeath = c;

 

Component_DeathAction d = new Component_DeathAction();

d.dropGoldChance = 90;

d.dripItemChance = 15;

d.explodeChance = 1;

b.cDeathAction = d;

 

So now we have a baddie that has 10 hitpoints, no armor, and might drop gold, an item, or explode when you kill him.

 

 

Where I'm confused is how does the update loop code look? It seems to me this will have the same code smell that my original inheritance method did. In other words, you'll constantly have to be checking things on each object. Instead of checking the type, we'll have to check the component:

 

foreach (Baddie b in baddieList) {

   if (b.cHealth != null ){

       // do health related stuff

   }

   if (b.cLocation != null) {

       // do location related stuff

   }

   if (b.cSpellCasting != null) {

      // do spell casting stuff

   }

   if (b.cDeathAction != null) {

      // do death action stuff

   }

}

 

This doesnt look that much better than all my tests for baddie type: if (theBaddie is Wizard) { ... , however I guess it does feel more flexible overall.

Share this post


Link to post
Share on other sites

You're still missing much of it.  Instead of testing for the subclass you are testing for null. You are treating your objects as plain old data.

 

Go read up on the Dependency Inversion Principle, linked to above.

 

Member functions should generally be verbs, not nouns. DoSomething(), not GetValue(). 

Share this post


Link to post
Share on other sites

Your last code approach goes in the ECS (Entity - Component - System) direction, but not quite.

 

You'd have entities (like your Baddie class), but entities wouldn't be limited to enemies. They'd also include the player, items, etc..

 

Then you have the components (like you wrote them) to store the actual data, since entites are just containers.

 

What you're missing is the systems.

 

These would iterate over all entities they can be applied to each update loop

 

e.g.:

class PlayerMovementSystem
{
    void Move(Entity e)
    {
        *update player position if any movement keys are pressed*
    }
}
class RenderSystem
{
    void Render(Entity e)
    {
        Renderer.DrawSprite(e.Sprite, e.Position);
    }
}

Share this post


Link to post
Share on other sites

Thanks again all.

 

For anyone who's interested, I've decided to fix my inheritance model and push forward with that. If I run into any more major hurdles, I'll probably read up on components and move in that direction.

 

However for now my original idea of using inheritance seems to be working. The main change I made was going from this:

 

foreach {Baddie theBaddie in _allBaddies) {

   if (theBaddie is Knight) { ... }
   if (theBaddie is Wizard) { ... }

   if (theBaddie is Slime) { ... }

}

 

to this:

 

foreach {Baddie theBaddie in _allBaddies) {

   theBaddie.Update();

   theBaddie.Move();

   theBaddie.Attack();

}

 

I still have all my Baddie subclasses (Knight, Slimer, Wizard etc), but they all now use virtual methods to expand on the simple/generic Update() Move() Attack() functions provided by the Base Baddie class. I no longer need to check the type of any Baddie because the virtual methods just magically do the right thing for me.

 

One of the big arguments I'm hearing against inheritance is that its not very flexible. "What if you wanted to create a Knight who could also cast spell? You would have to create another KnightWizard subclass for example". While I suppose thats true, I'm ok with that fact. I'm actually planning to make each new Baddie type significantly different from the rest, so its not likely that any new Baddie I'm going to design is going to be simply a merging of 2 existing Baddies. In other words I'm probably not going to create a KnightWizard because I already have a Knight and a Wizard and that would be lame.

 

If anything, I would maybe get rid of the Wizard class, and then add a boolean to the Knight class for canCastSpells. Now I have a Knight that can cast spells or not. Actually, could this be considered a small step in the direction of Entity/Component ? I know components are generally thought of as classes, such as SpellCastingComponent, however what if its just a boolean like canCastSpells? Isn't that a very simple implementation of components? If each Baddie has a canCastSpells option, isn't that kind of like a Spell component that I can add and remove (enable and disable)? Anyway, I'm not planning on doing much of this, just sort of thinking out loud.

 

I do have some very basic variables that actually let me significantly change the behavior of any specific Baddie, without having to create new custom Baddie subclasses. For example I can change the speed, damage, hitpoints, texture/color, sounds, and drop chances of a Slimer Baddie simply by tweaking its parameters. I dont need any new FastSlimer or RedSlimer or ArmourdSlimer subclasses. I can actually make it seem like there are 100 different types of enemies by just using the Slimer class.

 

Anyway I just wanted to post an update and see what all you experts thought. I really appreciate the comments so far and have been learning lots.

Share this post


Link to post
Share on other sites


In case you have mixed classes, like that spellcasting knight, I'd recommend using Interfaces.

 

While using interfaces like this can work, and it is certainly one method of implementing the Dependency Inversion Principle, it is not the only way to do it.

 

Interfaces are hard coded and they don't play well with component systems that are so popular right now.

 

If your system is primarily code based rather than data driven, and it is not component based, yes, that pattern can work. In fact, sometimes that pattern is used on the components themselves in an ECS.

 

However, that pattern tends to not be as extensible in the broader sense. It cannot be data driven. It requires hard-coding the interfaces, is difficult to modify an existing class, can lead to difficulties when multiple bases reuse a name or signature, can lead to difficult-to-resolve dependency chains, and so on. 

Share this post


Link to post
Share on other sites

However for now my original idea of using inheritance seems to be working. The main change I made was going from this:

 

foreach {Baddie theBaddie in _allBaddies) {

   if (theBaddie is Knight) { ... }
   if (theBaddie is Wizard) { ... }

   if (theBaddie is Slime) { ... }

}

 

to this:

 

foreach {Baddie theBaddie in _allBaddies) {

   theBaddie.Update();

   theBaddie.Move();

   theBaddie.Attack();

}

 

This is the correct way to handle it, let virtual routing handle the differences for you.
 
In OO, whenever you find you have "if" statements that acts on an object type to perform a different action you have something wrong in your code structure and should look at else you can handle it.
 
With your Baddie classes you should avoid creating a different  Subclass unless there is actually a difference that makes it work while otherwise you will create a maintenance nightmare for yourself. If you have a Gnome and a Goblin and they are the same apart from the name and damage they inflicted then they could share the same Subclass with a Name and Damage property etc, A factory object would be responsible for creating the instance and configuring it for the Monster type
Edited by WozNZ

Share this post


Link to post
Share on other sites
WozNZ,

Thats what I basically ended up with. There were a lot of folks saying definitely do not use inheritance for this, but I wanted to see the "why not" for myself. So far its going well. Maybe down the line if I add lots more complex baddies, I'll have to switch over to the component system that everyone is praising, but for now inheritance is working. 

 

With your Baddie classes you should avoid creating a different  Subclass unless there is actually a difference that makes it work while otherwise you will create a maintenance nightmare for yourself. If you have a Gnome and a Goblin and they are the same apart from the name and damage they inflicted then they could share the same Subclass with a Name and Damage property etc,

 

This was the key that I was originally missing. I can swap out textures and sounds, and have different hitpoints, armor, damage, speed, etc etc etc all without having to create a new subclass. A new subclass is only needed for drastically different functionality, like a whole new way of moving or attacking.

Share this post


Link to post
Share on other sites


Thats what I basically ended up with. There were a lot of folks saying definitely do not use inheritance for this, but I wanted to see the "why not" for myself. So far its going well.

The trick is that what you described is probably the direction that should be avoided.

 

My hunch, based on your description, is that you have something like this:  (Forgive errors, just typing as I go)

 

class Baddie {

 public:

 virtual void Update() {...}

 virtual void Attack() {...}

 virtual void Move() {...}

 ...

 

Then for each of your subclasses you build your own custom logic for each function.

 

This is what you see in many books that teach the language, so it is understandable.

 

Herb Sutter wrote about this in depth well over a decade ago in C++ and others were writing about it years before that. It applies to C++ just as well to C#, here is one such article.

 

Generally your interfaces -- the public methods -- should be non-virtual in the base class.  The code for them may call virtual functions that modify parts of the behavior, but the overwhelming majority should rely on the base behavior.

 

 

Let me give a real life example of that.

 

Working on a major golfing title on it's annual release. The game has many different game modes. "Stroke Play" (traditional golf), "Match Play" (counting holes rather than strokes, slightly shorter game), "One Ball" (players take turns hitting a single ball, the one getting it in wins the hole, with rules to let you set up bad shots for your opponent), and so on.  There were around 20 or so game modes that had accumulated over the years.

 

These game modes had a collection of functions. The behavior was modified in the way described above. The functions were pure virtual ("abstract" in C# parlance) so each subclass mostly copied the code for the functions from a game mode that was similar, then modified as necessary.

 

We needed to add a tiny bit of functionality to all the game modes. It was not much, basically a line of initialization, 2-3 lines to update a statistic, and a call for cleanup when done.

 

Unfortunately we discovered that several of the game modes --- over many years of modification by many different developers --- had been modified wildly from their initial purpose. Some specializations dropped large chunks of functionality. A few of them had copied bugs between themselves, where something was removed from one function with a comment about the removal, then nearly identical code with the same effect was present in another function with a comment to fix a bug of the missing functionality, then a comment elsewhere with with another copy of the code with a bug note that they weren't sure why it hadn't been called yet since it should have been in the early stages, but they called it again just in case.   In other cases there was duplication or near-duplication between many different game modes, often a bug fix in one mode with a comment, then another fix in a different mode that wrote the bug was the same so they just copied the solution. 

 

If this code had been implemented with those guidelines of a non-virtual public interface that does the core work and limited non-public virtual functions that slightly modify the behavior, most of those bugs could have been avoided. Each of the specializations could have been implemented and shared between modes perhaps through a query to the subclass with a protected virtual method, and the mode-specific changes could have been implemented with the base classes calling a private virtual method. 

 

When you allow the public interface to completely replace the internals by making it virtual, the net result is that the function itself is meaningless and can be modified to do anything at all. You might have a virtual function MakeBlue(), another virtual MakeGreen(), another virtual MakeRed(). Then a derived class author comes along and decides for this object all three versions should be overridden; not only should they all be blue, but they should also open a popup dialog and also update some statistics. Then another derived class comes along and overrides the Blue and Red function to become green, and adds statistics to those two methods but not the third. Repeat for several subclasses, each one adding slightly different behavior.  Before long the base class user discovers that the function names only have minimal correlation to what the functions actually do, and each subclass has diverged in functionality to the point where you start needing to know which subclass you are dealing with so you can use the base interface. 

 

So while inheritance is a powerful tool, and inheritance represents one of the strongest relationships in these programming languages, it is very easy to misuse. Many developers are taught how to use it, but it is less common to be taught why and when to use it, and why not and when not to use it, along with patterns of using it effectively.

Share this post


Link to post
Share on other sites

People talking about the component system are actually right in the long run, all depends on the complexity of your object, operations, game though. For a simple game without much permutation then inheritance is probably fine.

 

All the classical books on OO use stuff like a shape drawing app etc where you have Shape and inherit to create Rectangle, Triangle etc.

 

Inheritance will bite you hard down the line though as others have explained. Composition > Inheritance and using interfaces instead of base classes (unless pure abstract) also gives you far more flexibility and eases testing via mocks etc.

As others have pointed out your tests for null should not always be required. For components that you expect to always exist then why test them, if they are a null reference that is actually an invalid state and hence breaks whatever contracts you think your components should meet. If you expect your component to be constructed with specific components us something like a debug assert as you pass it in at construction time to catch when this is not the case.

 

Debug.Assert(comp != null) 

 

The other way to handle the composition if you think creating all the component classes is too much is to steal tricks from the functional world and use function pointers to minimise the component class scaffolding, depending on the complexity of your operations.

 

public class Baddie

{

    private readonly [Action|Func]<?...?> _baddieMover;

 

    public Baddie([Action|Func]<?...?> baddieMover)

    {

        _baddieMover = baddieMover;

    }

 

    public void Move()

    {

        _baddieMover(params here);

    }

}

The factory that creates your components injects the operations at construction time much like the component system. 

 

Action<int, int> = void Name(int param1, int param2);

Func<int, int> = int Name(int param);

 

Then you just need a class with a set of static functions for the different move type operations and just plug in the one required. You can also get into tricks like partial application if a function is not quite the right shape for your need via lambdas

 

Given 

 

void MoveNpc(Point location, Point vector, int speed)

 

you can reshape to a 

 

void MoveNpc(Point location, Point vector)

 

via

 

Action<point, Point> MoveWalkingSpeed = (location, vector) => MoveNpc(location, vector, WalkingSpeedConstant);

 

This also gives you access to the closure the lambda is created in to inject all manner of extra funkiness if required 

 

But this is all pushing you in yet another direction.

 

Small simple stuff then Inheritance is useable

Complex stuff with many permutations then composition is the only true path.

Edited by WozNZ

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement