38
\$\begingroup\$

I prefer OOP features when developing games with Unity. I usually create a base class (mostly abstracted) and use object inheritance to share the same functionality to the various other objects.

However, I recently heard from someone that using inheritance must be avoided, and we should use interfaces instead. So I asked why, and he said that "Object inheritance is important of course, but if there are lots of extended objects, relationship between classes are deep coupled.

I'm using an abstract base class, something like WeaponBase and creating specific weapon classes like Shotgun, AssaultRifle, Machinegun something like that. There are so many advantages, and one pattern I really enjoy is polymorphism. I can treat the object created by subclasses as though they were the base class, so that the logic can be drastically reduced and made more reusable. If I need to access the fields or methods of the sub-class, I cast back to the sub-class.

I don't want to define same fields, commonly used in different classes, like AudioSource/Rigidbody/Animator and lots of member fields I defined like fireRate, damage, range, spread and so on. Also in some cases, some of methods could be overwritten. So I'm using virtual methods in that case, so that basically I can invoke that method using the method from parent class, but if the logic should be different in child, I have manually overridden them.

So, should all these thing be abandoned as bad practices? Should I use interfaces instead?

\$\endgroup\$
12
  • 31
    \$\begingroup\$ "There are so many advantages, and one pattern I really enjoy is polymorphism." Polymorphism isn't a pattern. It's literally the entire point of OOP. Other things like common fields etc can be easily achieved without code duplication or inheritance. \$\endgroup\$
    – Cubic
    Jul 11, 2018 at 12:55
  • 3
    \$\begingroup\$ Did the person who suggested you interfaces were better than inheritance give you any arguments? I'm curious. \$\endgroup\$
    – Hawker65
    Jul 11, 2018 at 12:59
  • 1
    \$\begingroup\$ @Cubic I never said that polymorphism is a pattern. I said "... one pattern I really enjoying is the polymorphism". It means that the pattern I really enjoy is using "polymorphism", not polymorphism is a pattern. \$\endgroup\$
    – modernator
    Jul 11, 2018 at 13:00
  • 4
    \$\begingroup\$ People tout using interfaces over inheritance in any form of development. However, it does tend to add a lot of overhead, cognitive dissonance, leads to class explosion, frequently adds questionable value and usually doesn't synergize well in a system unless EVERYONE in the project follows that focus on interfaces. So don't abandon inheritance just yet. However, games have some unique characteristics and Unity's architecture expects you to use a CES design paradigm, which is why you should tweak your approach. Read Eric Lippert's Wizards and Warriors series describing game-type issues. \$\endgroup\$
    – Dunk
    Jul 11, 2018 at 22:29
  • 2
    \$\begingroup\$ I would say that an interface is a particular case of inheritance where the base class contains no data fields. I think the suggestion is to try to not build a huge tree where each object must be classified, but instead a lot of small trees based on interfaces where you can take advantage of multiple inheritance. Polymorphism is not lost with interfaces. \$\endgroup\$ Jul 13, 2018 at 11:14

8 Answers 8

67
\$\begingroup\$

Favour composition over inheritance in your entity and inventory/item systems. This advice tends to apply to game logic structures, when the way in which you can assemble complex things (at runtime) can lead to a lot of different combinations; that's when we prefer composition.

Favour inheritance over composition in your application-level logic, everything from UI constructs to services. For example,

Widget->Button->SpinnerButton or

ConnectionManager->TCPConnectionManager vs ->UDPConnectionManager.

...there is a clearly defined hierarchy of derivation here, rather than a multitude of potential derivations, so it is just easier to use inheritance.

Bottom line: use inheritance where you can, but use composition where you must. P.S. The other reason we may favour composition in entity systems is that there are usually many entities, and inheritance can incur a performance cost to access members on every object; see vtables.

\$\endgroup\$
8
  • 10
    \$\begingroup\$ Inheritance doesn't imply that all methods would be virtual. So it doesn't automatically lead to a performance cost. VTable only plays a role in dispatching the virtual calls, not in accessing data members. \$\endgroup\$
    – Ruslan
    Jul 11, 2018 at 13:44
  • 1
    \$\begingroup\$ @Ruslan I have added the words 'may' and 'can' to accommodate your comment. Otherwise, in the interests of keeping the answer concise, I didn't go into too much detail. Thanks. \$\endgroup\$
    – Engineer
    Jul 11, 2018 at 13:50
  • 7
    \$\begingroup\$ P.S. The other reason we may favour composition in entity systems is ... performance: Is this really true? Looking at the WIkipedia page linked by @Linaith shows, that you'd need to compose your object of interfaces. Hence you have (still or even more) virtual function calls and more cache misses, as you introduced another level of indirection. \$\endgroup\$
    – Flamefire
    Jul 11, 2018 at 20:07
  • 1
    \$\begingroup\$ @Flamefire Yes, it is true; there are ways to organise your data such that your cache efficiency is optimal, regardless, and certainly far more optimal that those extra indirections. These aren't inheritance and are composition, albeit more in the array-of-struct / struct-of-arrays sense (interleaved / non-interleaved data in cache-linear arrangement), splitting into separate arrays and sometimes duplicating data to match sets of operations at different parts of your pipeline. But once again, to go into that here would be a very major diversion which is best avoided for concision's sake. \$\endgroup\$
    – Engineer
    Jul 12, 2018 at 6:56
  • 2
    \$\begingroup\$ Please remember to keep comments to civil requests for clarification or critique and debate the content of the ideas and not the character or experience of the person stating them. \$\endgroup\$
    – user1430
    Jul 13, 2018 at 21:27
20
\$\begingroup\$

You got a few nice answers already, but the huge elephant in the room in your question is this one:

heard from someone that using inheritance must be avoided, and we should use interfaces instead

As a rule of thumb, when somebody gives you a rule of thumb, then ignore it. This not only goes for "somebody telling you something", but also for reading stuff on the internet. Unless you know why (and can really stand behind it), such advice is worthless and often very harmful.

In my experience, the most important, and helpful concepts in OOP are "low coupling" and "high cohesion" (classes/objects know as little as possible about each other, and each unit is responsible for as few things as possible).

Low Coupling

This means that any "bundle of stuff" in your code should depend on its surroundings as little as possible. This goes for classes (class design) but also objects (actual implementation), "files" in general (i.e., number of #includes per single .cpp file, number of import per single .java file and so on).

A sign that two entities are coupled is that one of them will break (or need to be changed) when the other is changed in any way.

Inheritance increases coupling, obviously; changing the base class changes all subclasses.

Interfaces reduce coupling: by defining a clear, method-based contract, you can change anything about both sides of the interface freely, as long as you don't change the contract. (Note that "interface" is a general concept, the Java interface or C++ abstract classes are just implementation details).

High Cohesion

This means to have each class, object, file etc. be concerned with or responsible for as little as possible. I.e., avoid large classes that do a lot of stuff. In your example, if your weapons have completely separate aspects (ammo, firing behaviour, graphical representation, inventory representation etc.), then you can have different classes that represent exactly one of those things. The main weapon class then transforms into a "holder" of those details; a weapon object then is little more than a few pointers to those details.

In this example, you would make sure that your class representing the "Firing Behaviour" knows as little as humanly possible about the main weapon class. Optimally, nothing at all. This would, for example, mean, that you could give "Firing Behaviour" to any object in your world (turrets, volcanoes, NPCs ...) by just a snap of a finger. If you at some point in time want to change how weapons are represented in the inventory, then you can simply do so - only your inventory class knows about that at all.

A sign that an entity is not cohesive is if it grows larger and larger, branching out in several directions at the same time.

Inheritance as you describe it decreases cohesion - your weapon classes are, at the end of the day, big chunks who handle all kinds of different, non-related aspects of your weapons.

Interfaces indirectly increase cohesion by clearly splitting off responsibilities between the two sides of the interface.

What to do now

There still are no hard and fast rules, all of this is just guidelines. In general, as user TKK mentioned in his answer, inheritance is taught a lot in school and books; it is the fancy stuff about OOP. Interfaces are both probably more boring to teach, and also (if you go past trivial examples) a bit harder, opening up the field of dependency injection, which is not so clear-cut as inheritance.

At the end of the day, your inheritance-based scheme is still better than having no clear OOP design at all. So feel free to stick with it. If you wish to, you can ruminate/google a bit about Low Coupling, High Cohesion and see if you wish to add that kind of thinking to your arsenal. You can always refactor to try that out if you wish to, later; or try out interface-based approaches on your next larger new module of code.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for detailed explanation. It's really helpful to understand what am I missing. \$\endgroup\$
    – modernator
    Jul 13, 2018 at 16:15
14
\$\begingroup\$

The idea that inheritance must be avoided is simply wrong.

There exists a coding principle called Composition over Inheritance. It says that you can achieve the same things with composition, and it's preferable, because you can reuse some of the code. See Why should I prefer composition over inheritance?

I have to say I like your weapon classes and would it do the same way. But i haven't made a game by now...

As pointed out by James Trotter, composition could have some advantages, especially in the flexibility at runtime to change how the weapon works. This would be possible with inheritance, but it's more difficult.

\$\endgroup\$
3
  • 14
    \$\begingroup\$ Rather than having classes for the weapons though, you could have a single "weapon" class, and components for handling what firing does, what attachments it has and what they do, what "ammo storage" it has, you could build up many configurations, some of which can be a "shotgun" or an "assault rifle", but that can also be changed at run time for example for switching out attachments and changing the magazine capacities etc. Or whole new configurations of guns can be created that weren't even thought of originally. Yes you can achieve something similar with inheritance, but not as easy. \$\endgroup\$
    – James T
    Jul 11, 2018 at 13:32
  • 4
    \$\begingroup\$ @JamesTrotter At this point, I think of Borderlands and its guns, and wonder what a monstrosity this would be with only inheritance... \$\endgroup\$
    – Baldrickk
    Jul 12, 2018 at 13:45
  • 1
    \$\begingroup\$ @JamesTrotter I wish that were an answer and not a comment. \$\endgroup\$
    – Pharap
    Jul 14, 2018 at 17:01
6
\$\begingroup\$

The problem is that inheritance leads to coupling--your objects need to know more about each other. That's why the rule is "Always favor composition over inheritance". This doesn't mean NEVER use inheritance, it means use it where it's completely appropriate, but if you are ever sitting there thinking "I could do this both ways and they both kind of make sense", just go straight to composition.

Inheritance can also be kind of limiting. You have a "WeaponBase" that can be an AssultRifle--awesome. What happens when you have a double-barrel shotgun and want to allow the barrels to fire independently--a little harder but doable, you just have a single-barrel and double-barrel class, but you can't just mount 2 single barrels on the same gun, can you? Could you mod one to have 3 barrels or do you need a new class for that?

What about an AssultRifle with a GrenadeLauncher underneath--hmm, a little tougher. Can you replace the GrenadeLauncher with a flashlight for nighttime hunting?

Finally, how do you allow your user to make the preceding guns by editing a config file? This is difficult because you've hard-coded relationships that might be better off composed and configured with data.

Multiple inheritance can fix some of these trivial examples somewhat, but it adds it's own set of problems.

Before there were common sayings like "Prefer composition over inheritance", I found this out by writing an overly complex inheritance tree (which was awesome fun and worked perfectly) then finding out that it was really difficult to modify and maintain later. The saying is just so you have an alternative and compact way to learn and remember such a concept without having to go through the whole experience--but if you wish to use inheritance heavily I recommend just keeping an open mind and evaluating how well it works for you--nothing terrible is going to happen if you use inheritance heavily and it might be a great learning experience at worst (at best it might work fine for you)

\$\endgroup\$
10
  • 2
    \$\begingroup\$ "how do you allow your user to make the preceding guns by editing a config file?" -> The pattern I generally do is create a final class for each weapon. For instance, like Glock17. First create WeaponBase class. This class is abstracted, defines common values like fire mode, fire rate, magazine size, max ammo, pellets. Also it handles user input, like mouse1/mouse2/reload and invokes correspond method. They are almost virtual, or splitted to more functions so that developer can override base functionality if they needed. And then create another class that extended WeaponBase, \$\endgroup\$
    – modernator
    Jul 11, 2018 at 18:39
  • 2
    \$\begingroup\$ something like SlideStoppableWeapon. Most of pistols have this, and this handles additional behaviours such as slide stopped. Finally create Glock17 class that extends from SlideStoppableWeapon. Glock17 is the final class, if the gun has unique ability that only has, finally written in here. I usually use this pattern when the gun has special fire mechanism or reload mechanism. Implement that unique behaviour to end of class hierachy, combine common logics as much as possible from parents. \$\endgroup\$
    – modernator
    Jul 11, 2018 at 18:42
  • 2
    \$\begingroup\$ @modernator As I said, it's just a guideline--if you don't trust it, feel free to ignore it, just keep your eyes open as to the results over time and evaluate the benefits vs pains every so often. I try to remember places I stubbed my toes and help others avoid the same, but what works for me may not work for you. \$\endgroup\$
    – Bill K
    Jul 11, 2018 at 20:43
  • 7
    \$\begingroup\$ @modernator In Minecraft they made Monster a subclass of WalkingEntity. Then they added slimes. How well do you suppose that worked? \$\endgroup\$
    – user253751
    Jul 11, 2018 at 22:33
  • 1
    \$\begingroup\$ @immibis Do you have a reference or anecdotal link to that problem? Googling minecraft keywords drowns me in video links ;-) \$\endgroup\$ Jul 13, 2018 at 14:34
3
\$\begingroup\$

Contrary to the other answers, this has nothing to do with inheritance vs. composition. Inheritance vs. composition is a decision you make regarding how a class will be implemented. Interfaces vs. classes is a decision that comes before that.

Interfaces are the first-class citizens of any OOP language. Classes are secondary implementation details. New programmers' minds are severely warped by teachers and books that introduce classes and inheritance before interfaces.

The key principle is that whenever possible, the types of all method parameters and return values should be interfaces, not classes. This makes your APIs much more flexible and powerful. The vast majority of the time, your methods and their callers should have no reason to know or care about the actual classes of the objects they're dealing with. If your API is agnostic about implementation details, you can freely switch between composition and inheritance without breaking anything.

\$\endgroup\$
4
  • \$\begingroup\$ If interfaces are the first class citizens of any OOP language I'm wondering if Python, Ruby, … are not OOP languages. \$\endgroup\$
    – BlackJack
    Jul 13, 2018 at 2:47
  • \$\begingroup\$ @BlackJack: In Ruby, the interface is implicit (duck typing if you will), and expressed by using composition instead of inheritance (also, it has Mixins which are somewhere in between, as far as I'm concerned)... \$\endgroup\$
    – AnoE
    Jul 13, 2018 at 5:44
  • \$\begingroup\$ @BlackJack "Interface" (the concept) does not require interface (the language keyword). \$\endgroup\$
    – Caleth
    Jul 13, 2018 at 15:17
  • 2
    \$\begingroup\$ @Caleth But calling them first-class citizens does IMHO. When a language description claims something is first-class citizen it usually means it is a real thing in that language that has a type and value and can be passed around. Like classes are first-class citizens in Python, as are functions, methods, and modules. If we are talking about the concept, well then interfaces are part of all non-OOP languages too. \$\endgroup\$
    – BlackJack
    Jul 13, 2018 at 19:22
2
\$\begingroup\$

Whenever someone tells you that one specific approach is the best for all cases, it's the same as telling you that one and the same medicine cures all diseases.

Inheritance vs composition is an is-a vs has-a question. Interfaces are yet another (3rd) way, appropriate for some situations.

Inheritance, or the is-a logic: you use it when the new class is going to behave and be used completely like (outwardly) the old class that you're deriving it from, if the new class is going to expose to the public all the functionality that the old class had... then you inherit.

Composition, or the has-a logic: If the new class just needs to internally use the old class, without exposing the old class' features to the public, then you use composition - that is, have an instance of the old class as a member property or a variable of the new one. (This can be a private property, or protected, or whatever, depending on the use case). The key point here is that this is the use case where you don't want to expose the original class features and use to the public, just use it internally, while in the inheritance case you're exposing them to the public, through the new class. Sometimes you need one approach, and sometimes the other.

Interfaces: Interfaces are for yet another use case - when you want the new class to partially and not completely implement and expose to the public the functionality of the old one. This lets you have a new class, class from a totally different hierarchy from the old one, behave as the old one in some aspects only.

Let's say that you have assorted creatures represented by classes, and they have functionalities represented by functions. For example, a bird would have Talk(), Fly() and Poop(). Class Duck would inherit from class Bird, implementing all of the features.

Class Fox, obviously, cannot fly. So if you define the ancestor to have all the features, then you could not derive the descendant properly.

If, however, you break the features into groups, representing each group of calls with an interface, say, IFly, containing Takeoff(), FlapWings() and Land(), then you could for the class Fox implement functions from ITalk and IPoop but not IFly. You would then define variables and parameters to accept objects that implement a specific interface, and then the code working with them would know what it can call... and can always query for other interfaces, if it needs to see if other functionalities are also implemented for the current object.

Each of these approaches has use cases when it's the best one, no one approach is an absolute best solution for all cases.

\$\endgroup\$
1
\$\begingroup\$

For a game, especially with Unity, which works with an entity-component architecture, you should favor composition over inheritance for your game components. It's much more flexible, and avoids getting into a situation where you want a game entity to "be" two things that are on different leaves of an inheritance tree.

So, for example, say you have a TalkingAI on one branch of an inheritance tree, and VolumetricCloud on another separate branch, and you want a talking cloud. This is hard with a deep inheritance tree. With entity-component, you just create an entity that has a TalkingAI and Cloud components, and you're good to go.

But that doesn't mean that in, say, your volumetric cloud implementation, you shouldn't use inheritance. The code for that might be substantial and consist of several classes, and you can use OOP as needed for it. But it will all amount to a single game component.

As a side note, I take some issue with this:

I usually create a base class (mostly abstracted) and use object inheritance to share the same functionality to the various other objects.

Inheritance is not for code reuse. It's for establishing an is-a relationship. A lot of the time these go hand in hand, but you have to be careful. Just because two modules may need to use the same code, it doesn't mean that one is of the same type as the other.

You can use interfaces if you want to, say, have a List<IWeapon> with different types of weapons in it. This way, you can inherit from both the IWeapon interface, and the MonoBehaviour subclass for all weapons, and avoid any issues with the absence of multiple inheritance.

\$\endgroup\$
-3
\$\begingroup\$

You seem to not understand what an interface is or does. It took me about 10+ years of thinking about OO and asking some really smart people questions was I able to have an ah-ha moment with interfaces. Hope this helps.

The best is to use a combination of them. In my OO mind modeling the world and people lets say, some deep example. The soul is interfaced with the body through the mind. The mind IS the interface between the soul (some consider intellect) and the commands it issues to the body. The minds interface to the body is the same for all people, functionally speaking.

The same analogy can be used between the person (body and soul) interfacing with the world. The body is the interface between the mind and the world. Everyone interfaces to the world in the same way, the only uniqueness is HOW we interact with the world, that is how we use our MIND to DECIDE HOW we interface/interact in the world.

An interface is then simply a mechanism to relate your OO structure with another different OO structure with each side having different attributes that must negotiate/map to each other to produce some functional output in a different medium/environment.

So for the mind to call the open-hand function it must implement the interface nervous_command_system with THAT having it's own API with instructions how to implement all the requirements necessary before calling open-hand.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .