3

I'm fairly new to object oriented programming and have a question I've stumbled upon regarding inheritance best practice.

I'm building a system for weapons in a game. I have a base class called Weapon and two subclasses – Melee and Projectile (such as knives and guns) – inheriting from Weapon.

All weapons have stats (like Attack Point) and parts (like Grip, Magazine and Blade). Booth guns and knives have Attack Point and Grip. But only guns have Magazines and only knives have Blade.

My question here is how to think when inheriting from Weapon:

  1. Thematically a weapon consist of parts so it would make sense to have the classes Melee and Projectile be in charge of all the parts that weapon consist of, so Melee have a Grip and a Blade, and Projectile have a Grip and a Magazine. But then I would duplicate the Grip field.

  2. Since both weapon types have Grip, should Grip be a part of the base class Weapon? It feels weird to break out Grip and put it by itself in another class.

  3. Weapon hold all parts, even if the subclasses don't use all of them? But the it feels like I will bloat the Weapon class.

Is there some sort of best practice? Thank you!

  • its a long read but related steve-yegge.blogspot.com/2008/10/universal-design-pattern.html – jk. Jun 8 '18 at 13:01
  • 7
    if history has taught us anything, its that you can put blades on guns and guns on blades – Ewan Jun 8 '18 at 13:11
  • 3
    Required reading: Eric Lippert: Wizards and Warriors – nvoigt Jun 8 '18 at 14:04
  • 3
    /* Inheritance is the least useful part of OOP, to my opinion; use it with caution. A composition of orthogonal concerns is usually a much clearer design. */ – 9000 Jun 8 '18 at 14:49
  • @nvoigt very interesting reading. I learned a lot. Is what he suggest at the end close to an Entity-Component-System pattern? – pend Jun 9 '18 at 18:20
7

Inheritance (driven to the extreme) is not an efficient approach in modeling something like weapons or enemies in a game. It's quite common to see people thinking about having Enemy, FlyingEnemy, GroundEnemy, then wanting to add more behaviour and realize that they'd end up with InvisibleFlyingEnemy and so on.

You're trying to decompose your weapon into their sub-parts, then trying to avoid duplication. One could ask you why you have different classes for the weapons, but your Grip class is shared by both (i.e. why not BladeGrip and ProjectileGrip). This can easily result in weird inheritance trees, and in the end none of them really makes you comfortable.

Since that doesn't work very well, generally in game development you avoid inheritance and use the Entity-Component-System architecture.

I'm not saying that you shouldn't have any inheritance, but there's no advantage in trying to get a 1-to-1 mapping between classes and what you see in the game. Disadvantages include serious inflexibility when you try to add new things and wasting time working on the invisible inheritance hierarchy instead of the visible game.

1

In a situation like this when inheritance doesn't quite fit, it is probably because these attributes such as grip, attack point, magazine, blade aren't what define the weapon. Therefore you're not dealing with a "WeaponWithGrip" or a "WeaponWithMelee", because conceivably you can mix and match. Since your classes can't (or at least shouldn't) have diamond-type references (inheriting from multiple classes), you should rethink what it means for a weapon to have these attributes.

Consider each of these aspects and decide what defines a gun and what is simply an attribute of that gun. You could say that an aspect defines a gun when its behavior needs to change entirely.

Suppose you have a Weapon class. This weapon has a MeleeExtension private member which could be absent. In your program, you need to inflict melee damage with your current Weapon instance. Check if your MeleeExtension is absent. If it is, your weapon cannot be used to inflict melee damage (no blade or other close range extension). If it does exist, then ask your MeleeExtension how much damage gets inflicted. Without knowing what type of weapon it is, your Weapon class can deal with melee encounters. MeleeExtension might then be implemented by Blade, Knife, or whatever.

The same can be said for Grip or type of projectile fired, etc. Now supposing for a second that for your booth weapon to work, you have to be in firing mode. This isn't something that can simply be fixed by making some sort of FiringModeExtension. This is an entirely new type of weapon, and thus you should treat it as such by creating a BoothWeapon which say, only lets you fire if the shooter is in firing mode.

If an extension doesn't make sense on Weapon, it is because it belongs to a new type of weapon (like for instance the BoothWeapon). Weapon should never need to know about the existence of such extensions to work properly (if it does, then your reasoning is incorrect in one sense or the other).

  • Booth guns in the question may have been a typo for Both guns. – Mike Partridge Jun 8 '18 at 13:08
1
  1. If the class Weapon has a grip, and the classes Melee and Projectile inherit from Weapon, they still "are in charge" (in your words) of that grip. Inheritance allows the subclasses to use a field of their subclass as if it was declared in the subclass itself (assuming the field is visible).

  2. I would rather see it as removing duplicate functionality from Melee and Projectile than breaking the classes up. It is true that using inheritance will result in having some features in the superclass, but that is not necessarily bad.

  3. A class should only reference the types it actually uses. Therefore the Weapon class should not have a magazine and a blade.

Using inheritance is not always the best choice. But it your example it might be a good idea because it allows you to clearly separate between the two types of weapons. Especially if you are new to object-oriented programming and want to learn about inheritance.

There are several articles discussing when to use inheritance vs. composition, for example this one or this one. A simple approach is to decide whether class A is a class B, or class A has a class B. In your case, we can see that a melee weapon IS a weapon and a projectile weapon IS a weapon. This indicates the use of inheritance.

But if you inheritance tree gets larger, it can be very confusing. So maybe try to think what scope you model will have. Will there be other kinds of weapons? Are there exceptions from your grip rule? Is it even necessary to model the grip, magazine, or blade as a class? If yes, what functionality do these classes carry?

1

There are 2 principle ways of building functionality:

  • Composition: Building up your functionality with a set of object instances that work together ("has a" relationship)
  • Inheritance: Changing behavior of something using the same object structure ("is a" relationship)

It sounds like concepts like magazines, bullet type, barrel, etc. would provide modifiers to the base gun. Similarly there may be other modifiers for melee weapons.

When you are designing your classes you have to ask yourself some questions to make sure you aren't overthinking things:

  • Is the composition of a weapon anything more than decorative? In other words, is the only real difference the flavor text, or do you intend on having parts you can swap out?
  • Are there differences in how you calculate damage, critical hits, or chance of hitting?

There are more questions but they don't really impact the decision between composition and inheritance. They only impact what attributes you need to keep track of.

Let's look at the simple case:

  • There's only one algorithm for everything and a standard set of attributes. Weapons can only be replaced as a whole entity and don't have swappable parts.

In this case there is no need for composition or inheritance. You only need to have a bunch of objects with the right attributes set.

Slightly more complicated case:

  • There's two algorithm sets: one for melee and one for projectile weapons
  • Within each set, there are some common attributes, but also some specialized attributes that are included in the calculation

In this case you would be looking at inheritance:

  • Weapon: common attributes and defining the names of the functions (calculateDamage, calculateCriticalHit, didHit)
  • MeleeWeapon: special attributes for melee weapons, and implement the functions defined in the base class
  • ProjectileWeapon: special attributes and methods for projectile weapons (like ammo count, reload, etc.), and implement the functions defined in the base class

This allows you to interact with the weapons generically when you actually use them, but the actual objects will handle all the differences in the calculations.

Finally, the most complex would be having replaceable parts. For these you would have the functionality built up of multiple objects working together. Your class attributes would have instances of those classes.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.