Default Implementations Using Delegation
Hey everyone! It’s my first post of the new year! Usually, I do a bunch of book reviews at the beginning of the year, and I will certainly do that in upcoming posts.
Also, don’t worry that I may have given up on my video series; I haven’t. I’m simply being a moron and doing other, less important things in my free time. I’m sure I’ll whip myself into shape soon enough.
Lastly, I’ve received a bunch of free copies of my book from Apress, so I’ll be coming up with some way to give those away sometime soon.
Onto THIS article now.
Intro
I need to start this article with a disclaimer: this isn’t really even a good idea. It’s really just a thought experiment that I did, and I’d like to show you how it could be done if you ever wanted to do it.
The idea came about because I had been reading about Template Methods a little earlier in the week, and then I was also reminded of Python’s Container ABCs, which use something akin to Template Methods for the default implementations of certain methods. Then I started thinking about how I could do default implementations without inheritance, since implementation inheritance is the root of many problems – but it’s NOT evil and DOES serve a purpose. In this case, sticking with the typical inheritance-based solution is probably the best. This is what inheritance was truly meant to do.
The Problem
I’ve already done an article similar to this, exploring how to replace the Template Method pattern with the Strategy pattern, but that idea is kind of backwards to what I want. Whereas that article has you make instances of the “Template” class that holds strategies of how to implement the non-template parts, in this case I want to make instances of a subclass that delegates to a template.
So, instead of constructing an object like TemplateType(Specialization())
, we just construct our SpecializationType()
which creates or otherwise accesses the template within. How would we do that?
Let’s look at an example using Kotlin and creating a List
type while looking only at add()
and addAll()
. Normally, using default implementations, you’d do something like this:
interface List<Element> { fun add(element: Element): Unit fun addAll(elements: Iterable<Element>): Unit { for(element in elements) add(element) } }
If your language doesn’t allow implementations of methods on interfaces (are there any left, besides older versions of Java?), you’d normally have to go the old-fashioned route of then creating an abstract base class named BaseList
or AbstractList
to inherit from that contained the default implementations.
Then you could create a MyList
class that inherits from the interface (or the abstract base class, if you went that route) and only implements add()
, leaving the default version to do what is needed.
That’s the inheritance way, but now we need to see the delegation way.
Solution
Let’s start back at the interface, which is the same but without any implementation.
interface List<Element> { fun add(element: Element): Unit fun addAll(elements: Iterable<Element>): Unit }
Now, we make the class that has the default implementation:
class DefaultListImplementation<Element> { fun addAllTo(list: List<Element>, elements: Iterable<Element>): Unit { for (element in elements) list.add(element) } }
Alternatives include having the default implementation class take the subclass instance in through the constructor (but that creates a circular reference, so I recommend not doing so), making the class a Singleton (which is okay, since it should be stateless), or having all the methods be standalone functions (static methods, if your language doesn’t allow functions).
Then, your new MyList
will look something like this:
class MyList<Element> : List<Element> { val delegate = DefaultListImplementation<Element>() override fun add(element: Element) { //implementation goes here } override fun addAll(elements: Iterable<Element>) { delegate.addAllTo(this, elements) } }
And that’s all there is to it. Surprisingly simple.
Down the Rabbit Hole
You could start to go a little crazy if you’d like and use dependency injection for the template delegate. The default implementation objects would become their own combo Template Strategy pattern. For example, DefaultListImplementation
could inherit from an interface that other “default” implementations could be implemented from, and then MyList
could accept an object of that type in its constructor, allowing you to customize MyList
‘s “default” implementation.
Personally, this seems a bit ridiculous, but if you ever have need of this article’s base pattern, then maybe it’s because you need the “template” to be “strategy-ized” in the first place.
Outro
So yeah, that’s the pattern. Like I stated at the beginning, this isn’t necessarily even a good idea in practice; it’s just a thought experiment that I thought I’d share with you guys. Stick with the old inheritance-based way, for the most part. Maybe reading this helped you to learn how to apply a similar thought process that you can use somewhere down the road to improve some of your code.
Thanks for reading!
Reference: | Default Implementations Using Delegation from our WCG partner Jacob Zimmerman at the Programming Ideas With Jake blog. |