4

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!

5
  • its a long read but related steve-yegge.blogspot.com/2008/10/universal-design-pattern.html
    – jk.
    Jun 8, 2018 at 13:01
  • 10
    if history has taught us anything, its that you can put blades on guns and guns on blades
    – Ewan
    Jun 8, 2018 at 13:11
  • 3
    Required reading: Eric Lippert: Wizards and Warriors
    – nvoigt
    Jun 8, 2018 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, 2018 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, 2018 at 18:20

6 Answers 6

8

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.

2

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).

2
  • Booth guns in the question may have been a typo for Both guns. Jun 8, 2018 at 13:08
  • A BoothWeapon is a .41 caliber Deringer pistol. Though Henry Rathbone assures us that it also can sometimes be an edged melee weapon, possibly a Deringer with bayonet affixed.
    – J_H
    Nov 17 at 21:16
2

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.

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 superclass 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

Humans love to build hierarchies of items, such that an items clearly fits within a tree of knowledge. Examples of this include the various Taxonomic ranks of Biology (there are competing ones) https://en.wikipedia.org/wiki/Kingdom_(biology)

As the early programmer that designed object-orientation was a Biologist, he added in a type system that supported this idea. Everything was an "Object" and items were cleanly divided into smaller groupings based on the programmer's organizational model.

The problem with this is the same problem that exists in Biology. Often there are items that must span very large branches. They tend to fit "somewhat" in at least two (or more) branches of the tree. This leaves the programmer with a few choices:

  • Make a new tree branch and treat the object as an oddity.
  • Put it in the best fitting branch, and try to handle the bits that don't fit with odd workarounds.
  • Restructure the tree.

The last choice is the best from a design point of view, but the hardest to actually achieve, as everyone who encountered your organization will then have to be updated to the new organization. When dealing with programming, this means all dependent modules need to be updated / rewritten.

With time, you will realize that the hierarchies are useful, but they shouldn't be organized by "what the item is" but rather by "what the item can do", and by clearly indicating that an item can live in many "functional, as in what it can do" branches. This is why many will caution against long lists of derived classes, where one class is derived from another. It's an improvement on the original ideas laid down in the 1950's / 1960's.

So the best practice is a bit at odds with most early texts that support a "a triangle is a shape, that's a object", or "a dog is a pet that's an animal that's an object". Instead you need to look at your program to decide the hierarchy.

"a triangle is an area giver and a side giver and a list of drawable items giver" will have no trouble in adding new drawable items, items with areas, or items which count sides. Oddly enough, if you opt for "a triangle is a closed shape is a shape is an object" you might find that by placing drawing routines in shape, you could complicate the inclusion of circles, as you probably built the drawing routines to handle lines. Fixing them in this hierarchy means you now need to update triangles so they can inherit the drawing of curved lines, which is odd and the primary reason one now recommends very short inheritance chains, with the upper level names being mostly interfaces (so you can add many of them) avoiding updates to classes that don't participate it whatever new functionality is being added to a traditional type tree.

0

And if the error is on the logic? I mean, should grip be assigned to a weapon if this is a property coming from who holds the weapon? Maybe that is the reason it's hard to define if should be in weapon object or in melee/projectile.

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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