fbpx

Composition over inheritance: tackle the “Template Method”

In this article we’re gonna talk about composition over inheritance but not approaching this topic from a theoretical point of view (you can find dozens of good articles online by googling the topic), but instead by providing an example of something that in literature is a well-know pattern: Template Method.

Template Method

Template method is a behavioral design pattern conceived to encapsulate an algorithm skeleton where some methods (at least one) are declared abstract in order to let concrete classes extend the “template” and provide a concrete implementation. This pattern is an example of Inversion Of Control, which I’ve already written about. Based on its nature, Template Method strongly relies on the inheritance principle, but despite this, we can reach pretty much the same results by using composition.

Disclaimer

With this article I’m not advocating that one approach is better than the other; as a matter of fact I’ll try to highlight pros and cons of both. This article is written because I was searching for alternative “compositive” implementations of “canonical” design patterns and I didn’t found a single one for Template Method.

Example

Based on the example linked above, here’s a slightly-modified code:

Above is shown the template method code. Below, the “client” one

EmailVerificationUserRegister was randomly choosen and you can both choose another concrete implementation, or configure it with dependency injection mechanism–that’s not reported here in order to keep the complexity as low as possible.

Composition

We have two alternatives (perhaps there could be more but those are what I found): strategy pattern and closure as callback. Starting from the latter–which is the worst of two but I still wanted share–I ended up with the following code:

Talking of closure as callback, a closure is an anonymous function (called also lambda) introduced back in PHP 5.3 and basically is a snippet of code (a function, indeed) that you don’t need to declare but just use “inline” and inherits the scope of where it is defined (unless explicitly binded to other context). In order to obtain the same result as the inheritance example, we should modify one of the concrete implementations as follows

Pay attention to call_user_func_array and to the fact that UserRegister::register has no typehint on the callback. I could have done something like

but call_user_func_array is much safer. Moreover I needed to introduce UserRegisterInterface in order to use it in Client class (that I won’t show again because the only thing that changes is the typehint in its constructor).

The second way is to use something similar to a strategy pattern as follows:

Conclusions

To summarize, beside the advantage of composition over inheritance that you can find pretty much everywhere on the web, I can take to your attention what follows:

  • With inheritance, you can inherit just from one class (at least in PHP). The example above is fine but can be hard to change if something changes in the behavior.
  • With inheritance, you need to test abstract class and all concrete classes as well: from my point of view, skipping concrete classes testing is just wrong as the extension is just an implementation detail. No tests on these classes means no tests for them at all.
  • With closures, you can’t ensure that parameters passed from the caller are consistent with function definition: imagine something changes in the way the register class invokes the callback or manages its results: you won’t notice as long as you don’t have functional/integration/e2e tests–that seems necessary if using closures. The return type could also be an issue; in fact I needed some extra control code in order to check the consistency.
  • With closures, in the first example, it’s also troublesome to refactor the method name as it is a string (even if modern IDEs could help you a lot in this, is just not fine).
  • With strategy you can easily adapt the code if the behavior changes. Moreoever you can have unit test that aren’t just a copy/paste of abstract class tests (in case of inheritance). Of course you need to a bit more “context switch” to understand how the code works but from my point of view, it’s a good tradeoff.

That’s all.

Happy coding! 🙂

DonCallisto
DonCallisto
Articoli: 21

Lascia una risposta

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.