Mixins, Composition, and Inheritance Guidelines

General discussion about LÖVE, Lua, game development, puns, and unicorns.
Post Reply
User avatar
Codex
Party member
Posts: 106
Joined: Tue Mar 06, 2012 6:49 am

Mixins, Composition, and Inheritance Guidelines

Post by Codex »

After having read up these subjects for the past month and testing these out together, I've hit a roadblock.

When to use what, why, and how to do it effectively?

Some relevant background:
  • I'm using middleclass.lua
  • I have a large code base with many different items, abilities, players, etc.
  • I'm in the middle of a huuuuuge refactor being done on pretty much all my OOP code.
Years ago when the repo was created I had a vague understanding of OOP, so I ghetto hacked my objects into a convoluted monstrosity with different components splashed across dozens of files. It was messy and ugly.

Now I have managed to make the code use classes correctly. But I noticed one thing in particular, parts of the code had use for mixins, some parts could use composition, a few pieces could use inheritance, and a couple of these overlapped. And when I try to get these together, I'm not sure how to implement the design? And what criteria needs to be met to determine which is used?

Kikito has discussed mixins and compositions in detail and another post where he described that classes are not needed in most cases.

I'm trying to nail down a concrete guideline for myself (and maybe others) in this post that is easy to follow. So far I'm under the impression that:

Inheritance is used:
  • When there are many objects and few are in specialized forms (possibly 1-2 objects)
Mixins are used:
  • When there are many objects and a good portion will need to share methods
  • Should mixins share more than just functions? Also include properties or arrays?
Composition is used:
  • To seperate a large object with properties into chunks?
  • Possibly by putting another class inside of a class?
  • Would it be limited to functions, tables, or values?
When to avoid all the above:
  • When an object does not need to be initialized? (something like a singleton)
Fuzzlix
Citizen
Posts: 60
Joined: Thu Oct 13, 2016 5:36 pm

Re: Mixins, Composition, and Inheritance Guidelines

Post by Fuzzlix »

Codex wrote: Sun Nov 19, 2017 1:52 am I'm trying to nail down a concrete guideline for myself (and maybe others) in this post that is easy to follow.
The question you ask, i ask myself to again and again each project. I asume there is no absolut true answer. I choose a aproach, i can most easily implement. I am a fan of the KISS-principle. If structures, dependecy graphs and/or guidlines becomes zu complicated i did something wrong. e.g. for some projects i use 30log, for others middleclass, I have a tiny set of modules i use in my projects but i change those modules as needed, so the module xyz in project x is a little bit different than in project abc and i need to keep both versions in the project backups.
Some simple coding principles and guidlines are very usefull to get started but to overcomplicated rulesets/guidlines are more a barrier than a usefull coding help.

I am not shure this answer is the answer you expected? :)
User avatar
Codex
Party member
Posts: 106
Joined: Tue Mar 06, 2012 6:49 am

Re: Mixins, Composition, and Inheritance Guidelines

Post by Codex »

I agree with the principles behind KISS, and for the most part it works well with small projects. But once your project reaches a large enough size, things start to become more complicated. Managing these complications in an orderly fashion is ideal but not always simple. I'll give you a few specific examples I am encountering right now...

Code Example 1 - Mixins and Compositions

An object in the game is established like so,

Code: Select all

local class = require('middleclass')
Crowbar = class('Crowbar', ItemBase)

Crowbar:initialize(item_condition) self.condition = item_condition end

Crowbar.FULL_NAME = 'crowbar'
Crowbar.WEIGHT = 3
Combining Mixins and Composition Together

Code: Select all

Crowbar.weapon = {ACCURACY = 0.10, DICE = '1d4'} -- group our weapon vars into a table

local IsWeapon = { --mixin methods
  getAccuracy = function() return self.weapon.ACCURACY end,
  getDice = function() return self.weapon.DICE end,
  attack = function(player, target, condition) --[[do something with condition]]-- end,
}
Crowbar:include(IsWeapon)

crowbar = Crowbar:new(3)
crowbar.weapon:getAccuracy()
crowbar.weapon:attack(some_player, some_target, self.condition)
Notice self only references the weapon table when using IsWeapon mixins! We have to pass crowbar.condition as a argument and possibly even crowbar itself since we do not have access to the main item vars that might be needed...

Using Mixin Only

Code: Select all

-- spread these vars in the object publicly
Crowbar.ACCURACY = 0.10
Crowbar.DICE = '1d4'

local IsWeapon = { --mixin methods
  getAccuracy = function() return self.ACCURACY end,
  getDice = function() return self.DICE end,
  attack = function(player, target) --[[do something with condition]]-- end,
}
Crowbar:include(IsWeapon)

crowbar = Crowbar:new(3)
crowbar:getAccuracy()
crowbar:attack(some_player, some_target)
Here our main item table is polluted with weapon vars and methods, but this also gives the ability to access other public variables in item instead of having to pass them as an argument when using a weapon mixin.

Is there a better way to combine the two without the drawbacks described? Should I be wary of passing the item values as an argument to the weapon functions?

Code Example 2 - Replacing Classes with Composition:

A big class in the game that has many subsystems is Player,

Code: Select all

local log = require('player.log')
local inventory = require('player.inventory')
local skills = require('player.skills')
local condition = require('player.condition')
local class = require('middleclass')

Player = class('Player')

Player:initialize(...)
  self.hp = 50
  self.log = log:new()
  self.inventory = inventory:new(self)
  self.skills = skills:new(self)
  self.condition = condition:new(self)
end
While log, inventory, skills, and condition are all subsystems, I do not think there is a need for them to be classes. None of those systems need to deal with multiple instances. Yet they do need to initialize only once for each player.

To initialize the subsystems and get their raw stored values (without the funcs) it would look like this:

Code: Select all

Player:initialize(...)
  self.log = {}       -- store msgs inside
  self.inventory = {} -- store items inside
  self.skills = 0     -- store value using bitmasking
  self.condition = 0  -- store value using bitmasking
end
When I try to put this into a module alone without initializing as a class it causes problems,

Code: Select all

local log = {}

function log.append() end -- do stuff
function log.read() end --printout the log for player
function log.insert(msg) end -- add msg to log

return log


All players that self.log = require('log') end up using the same log table from the module to store messages! Each player needs their own unique self.log = {} instead of using the one from 'log.lua'! I'm not sure how to resolve this efficiently? Should I be using mixins to add the functions to the table? But mixins would only work if the self.log was already a class, so that is an issue.

Code Example 3 - Inheritance vs Mixins:

So there are plenty of mixins in my game. IsWeapon, IsReloadable, IsMedical, IsArmor, etc. How do I know which should be a mixin and which should be a subclass with inheritance? Really, it could be coded both ways.

Via Inheritance

Code: Select all

Item = class('Item')
IsWeapon = class('IsWeapon', Item)  

IsArmor =        class('IsArmor',      IsWeapon) --> Item --> IsWeapon --> IsArmor
IsMedical =      class('IsMedical',    IsWeapon) --> Item --> IsWeapon --> IsMedical
IsReloadable =   class('IsReloadable', IsWeapon) --> Item --> IsWeapon --> IsReloadable
Via Mixins

Code: Select all

Armor =           class('Armor',      Item):include(IsWeapon, IsArmor)
RangedWeap =      class('RangedWeap', Item):include(IsWeapon, IsReloadable)
Medical =         class('Medical',    Item):include(IsWeapon, IsMedical)
I guess if an object is using a lot of mixins it might be a pain to use solely inheritance as your inheritance tree will get very long quickly but other than that, I don't see why one would be preferred over the other.
Last edited by Codex on Sun Nov 19, 2017 4:59 pm, edited 3 times in total.
User avatar
bartbes
Sex machine
Posts: 4946
Joined: Fri Aug 29, 2008 10:35 am
Location: The Netherlands
Contact:

Re: Mixins, Composition, and Inheritance Guidelines

Post by bartbes »

Please don't double (or triple) post.
User avatar
Codex
Party member
Posts: 106
Joined: Tue Mar 06, 2012 6:49 am

Re: Mixins, Composition, and Inheritance Guidelines

Post by Codex »

bartbes wrote: Sun Nov 19, 2017 4:39 pm Please don't double (or triple) post.
Whoops! :o I'll edit my earlier post to include the relevant info. Also are there no spoiler tags available for the forums? It would make the lengthy post alot more compact.

Edit - and now those posts are included in the original response. So they can all be deleted now, thank you.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Mixins, Composition, and Inheritance Guidelines

Post by kikito »

I'm trying to nail down a concrete guideline for myself (and maybe others) in this post that is easy to follow.
Ah, I understand you. I have a name for the feeling. I call it The Struggle.

I have this friend. He's younger than me. Designer. We have worked together in some projects. He's a very fine individual, very intelligent. But he's got this personality trait: he likes categories. Probably because he's a bit OCD. Everything must be black, or white. Or red. Something. Things need to be categorized following simple, concrete rules.

One of the things he hates to hear the most is "it depends". So we play this game. He starts asking me a technical question. I smile and I answer "It depends", anticipating the "It depends ON WHAT?", which immediately follows. Then I start enunciating the different factors that can affect the outcome. But he loses his patience and he says "So it is impossible to know". And I smile and say "No, it is impossible to know quickly".

So now I tell you: It depends.

I realize that this is a very unsatisfying answer to a lot of people - as it is for my friend. I will try to expand on the "on what" a little bit.

"What is good programming?" is like "What is good surgery?". First, the definition of "good" is not fixed. Second, there are a multitude of factors affecting the outcome, so the answer can not be categorical - it fluctuates depending on its circumstances. One of the most important factors to me is people.

It would seem that programming is about "making machines do things". And that is correct - but unuseful. It's like saying that "cooking is applying chemical transformations to organic compounds". By leaving the humans out of the context, we lose the context. This is clearly apparent after a software has been running for some time (say, 6 months) and it is out of the "initial version mode". Then it gets into "maintenance". The first bugs are reported. The first change requests are received. There's the need to make changes in the code. These changes have to be performed by humans. And if the initial was written "just so that the machines do things", making those changes will be extremely difficult.

So I would say that's one of the factors software has to take into account: it has to be easy to change.

At the same time, since these changes have to be performed by humans, code needs to be written in a way that is easy for humans to understand it. One way I like to think about it is: Code is not what you tell the machine to do: it is what you tell future programmers that you want the machine to do. Being "easy to change" is to me more important than "making the machine do what is supposed to do" - after all, if it's easy to change, I can change it to make the thing it is supposed to do.

"Easy to change" and "easy to understand" are not always aligned (discovering this broke my heart a little bit). Let me explain that.

There's this class of things called "dependencies". "Having a dependency" basically means "knowing something about another piece of code". If your code uses a function like love.graphics.print, then your code gains some dependencies: it knows the function name, and it knows the parameters it takes.

Some dependencies are necessary - if we don't depend on love.graphics, we can't use graphics at all. At the same time, if we want software to remain changeable, we must try to minimize dependencies. That is why using globals is very bad: globals are huge dependencies, which make the code more difficult to modify than the alternatives (parameters).

Superclasses are big dependencies as well. You know their names, as well as all their methods, the params each method takes, and their constructor, which is a "special" method. Sometimes the superclass has to leave "hooks" for the subclasses to work, so they "kindof know" what their subclasses are doing.

Mixins are a similar deal: you know their name, their methods and params. They don't have a constructor so in that sense they are a little better than inheritance at dealing with dependencies.

Composition offers a different deal: components themselves have almost no dependencies. The "things using components" (some call these "Entities") have the components as dependencies, but as long as their only role is "being a group of components", and don't add "special extra code", then the dependencies should remain quite manageable.

But we have been talking about "easy to change". What about "easy to understand"?

Well this is more complex because there is some subjectivity involved. What is easy to a senior dev who wrote the whole thing might not be easy for a junior dev who joined the company last week. But still, we can make some assumptions.

Most programmers understand inheritance quite well, because they study it.

Composition is not difficult to understand on itself, but new programmers will have difficulties understanding *why* is used.

Mixins are a bit niche and will take some effort to understand from a lot of programmers, especially new ones.

So, there you have it: depending on what you want to do, and who you are, the answers are different.

In my particular case:

* I use classes most of the time. I don't like inheritance very much, but I do like other aspects of OO, like "creating a bunch of similar entities with custom attributes" or "the decision about what is executed is a responsibility of an object to which you are just sending a message". When I am doing a library I don't use middleclass just to avoid adding a dependency to the lib.
* I try to use inheritance only when I'm certain I will have a very small tree: 2 levels with maybe 1 top class and 3 or 4 subclasses. More than that and I start getting nervous. I sometimes have a base class with very abstract stuff: on the level of draw and delete.
* I try to use composition to deal with "the important logic" of my app. So if I'm doing a videogame, my enemies, player, etc should be entities composed of small, separate components. Most of my libraries are thought to be used as components. The entities and components can be instances of classes. I'm not in the Entity-Component-System bandwagon. I use the dependencies to know how to group stuff together into components. My typical components will have names like "PhysicalBody" or "Projectile". Both the Entities and the Components could be implemented using inheritance, but using many small trees instead of a big-ass one.
* I use mixins when I want to share a non-important bit of logic among several classes (like debugging, logging, or presenting stuff on the screen), when that logic can be expressed as a bunch of functions with no constructor (like "Toggleable" or "Solid"), and when I am too lazy to divide stuff into proper components.

Also note that this set of rules isn't fixed; it depends on me and I change over time.
When I write def I mean function.
User avatar
Tjakka5
Party member
Posts: 243
Joined: Thu Dec 26, 2013 12:17 pm

Re: Mixins, Composition, and Inheritance Guidelines

Post by Tjakka5 »

While I do not have an answer to how you should use classes and mixins properly, I can however show you the way of Entity Component Systems.

The Entity Component Systemparadigm (Or ECS for short) follows the "Composition over inheritance principle", which addresses your current problem.

The paradigm is made up of 3 kinds of basic building blocks:
1. Entity
Every object in your game is an Entity.
An Entity is simply a table which holds an "ID"and a list of:

2. Components
Components are so called "Data Bags". They have a name, and can be created and attached to any Entity.
When attached to an Entity the Entity is labelled as being in possession of that component, and can then be manipulated by:

3. Systems
Systems hold all the logic; they move the Entities, they draw them, they handle input, etc.
Systems apply their function only to Entities that are in possession of certain Components.
For example: A "SpriteRenderer" System will only apply its logic to Entities with the Components "Position" and "Sprite. Where "Position" holds the x and y value, and "Sprite" the texture, and potential other modifiers such as color, rotation, shaders, etc.


This makes it that, instead of worrying about the inheritance in your project, you can focus on your composition.
Here's a small example that should show it's strengths versus OOP:

We are working on a big Rogue Like game, think Binding of Isaac, with lots of different enemies.
In our OOP approach we have a "Zombie" class, it inherits from "Monster".
We also have a "Bomb" class, it explodes when triggered.

However, now we want a "Exploding Zombie". Obviously it should inherit from "Zombie", but from there?
Should it somehow also inherit from "Bomb"? Should there be some kind of "Exploding" mixin? What if I want "Exploding" variants for all the monsters in my class? Do I need to make an extra class for all of them?

There's a bunch of answers to these questions, but all are very unsatisfying.

Now in an ECS approach we would need all the components for a zombie: "Position", "Health", "Moving", "isEnemy".
For a Bomb we would need the following components: "Position", "Explodable".

We can reuse "Position" for both of these components, and if we now want a exploding zombie we just take a zombie and attach the "Explodable" component. The system handling explosions would acknowledge our entity having the "Explodable" component and make it so it explodes when triggered. It doesn't care that it moves around or has health. It just does it.


Using this approach can be little tough to get used to at first; you may still want to make things from an OOP approach, but you need to realize that instead of making "Things that does this exact behaviour" you are instead trying to code "Behaviours that when combined represent this thing".

If you decide to get into it, I would recommend using HooECS and read up a bit more about the specifics. This was just a very broad "Hey this exists" message.

Hope it helps!
User avatar
SiENcE
Party member
Posts: 805
Joined: Thu Jul 24, 2008 2:25 pm
Location: Berlin/Germany
Contact:

Re: Mixins, Composition, and Inheritance Guidelines

Post by SiENcE »

@Tjakka5: But this are the coding basics.

* data abstraction
* seperation between Command (Logic/Processing) & Data Holder classes

Funny, like the game industry find new names for common coding principles.
User avatar
kikito
Inner party member
Posts: 3153
Joined: Sat Oct 03, 2009 5:22 pm
Location: Madrid, Spain
Contact:

Re: Mixins, Composition, and Inheritance Guidelines

Post by kikito »

The difference between Entity-Component-System and OOP is the place where it is decided which code is run.

In OOP that decision tree starts in the object instance and goes "up" from there: to its class definition first, and from there to superclasses and mixins. It's a tree of decisions which starts with a message sent to an object, and its state can play a role.

In ECS the decision tree is different. Objects (Entities) are just data grouped together, and they have no code. The identity of each object plays no role: what matters is *what it is formed of* - its components. Systems are basically loops which parse the collection of all entities (usually with some prefiltering), detect the ones that have a particular component, and apply an operation to it. The whole setup is simple, but

Incidentally, I think one of the things both paradigms have in common is that they both have crappy names. OOP would be more appropriately named "message-oriented", according to the inventor of the term. ECS could be called Composite/Filter/Effect - Using two worlds for Entity and Component seems wasteful when Composite encapsulates both very well. Effect is more concise than System, and Filters are used so often in the Systems that they deserve to be on the paradigm name.
When I write def I mean function.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest