How I created a strange design pattern and how it proved to be so incredibly useful.
Like many amazing finds or new things, this particular solution came from a tricky problem. To understand the issue best, I have created a small diagram below that shows a simplified hierarchy of the action classes:
*This diagram is not an actual representation of the classes, it just demonstrates the basic idea.
As you can see at the bottom of the hierarchy, a particular action has 3 classes (a shared implementation and then child classes for both the server and client specific code). This means the client and server implementations derive from a common base and it is easy to write common code for both as well as functionality for client / server using either separate calls or virtual functions that are overridden. Above these leaf classes we have many parent classes that handle various different functionalities.
The issue here, which is actually a relatively common issue, is this:
What happens when we need one of the parent class functionalities to have common server or client code? It would look like this:
Ah, the classic diamond inheritance; Both the shared implementation and the server or client specific parent need to derive from the classes above, because their callbacks and variables rely on that information. I guess you could draw the diagram with the connection either how it is (with Fireball a child of Projectile Client) or Fireball Client as a child of Projectile Client. Either way, the issue is the same (the client has ambiguous parent classes). What we need is for Fireball to have derive from Projectile Client, but only on the actual client (because otherwise Fireball Server will be trying to derive from a class that it doesn't know about.
In the past, the solution has just been to put virtual functions in the shared implementation and then for each child that needs to, will implement the functionality themselves. This is especially annoying though when there are many classes and/or when the implementation is almost identical. Bonus annoyance if there is a lot of code for the implementation(s).
So we solved this by coming up with the idea of what we called 'template parent insertion'. I am sure there must be a formal name for this but I have not been able to find it (let me know if you know!). At first glance it appears very similar to The Curiously Recurring Template Pattern (CRTP), but in reality it is quite different, and is actually solving a different problem.
So how does it work? First of all, here is a diagram to help explain:
*I will attach some source code below to complement this diagram.
Basically the idea is that the shared class (Fireball in this case) is a template class which derives from the template itself, The child classes derive from this and pass in the class that they want Fireball to inherit from, via the template. Hence why, at the time, we sort of called it 'template insertion'; because it allows you to stick a class in-between. The server or client can insert different classes to each other, or one can insert a class and the other doesn't need to, which provides a great design for class hierarchies like this where we have a separate client and server implementation with a shared base.
The main issues I faced with this design is compiling with GCC on Linux. Firstly, in the base class (in this case Fireball), when referencing functions or members from the parent class (the template class), I had to always use this-> or it would be a compilation error. The other issue, in the same scenario but for template functions called from members or functions from the parent class, I had to always use the following syntax:
this->GetObject().template GetComponent< Health >();
GetObject().GetComponent< Health >();
Details of this issue can be read here.
So there it is, a relatively nice solution to a common problem. Although I'm sure in most situations you are better off rethinking your design such as trying to free standing helper functions, or creating a separate implementation that doesn't derive from the Projectile, or even just using regular composition (storing a member that handles the specific implementation that can't be in shared code). For myself and the situation I had, this proved to be the easiest method to achieve what I needed, so maybe it will prove useful for others too. Programming has infinite possibilities and there is no perfect solution to everything. What is important though, is having as many patterns, tricks, designs and knowledge as possible to construct the best possible solution you can for a particular problem!
Example source code available here.