19
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 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.
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 was written because I was searching for alternative "compositive" implementations of "canonical" design patterns and I didn’t found a single one for Template Method.
Based on the example linked above, here’s a slightly-modified code:
abstract class UserRegister
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
final public function register(User $user): void
{
$user->enable();
if (!$this->sendVerification($user)) {
return;
}
$this->em->persist($user);
$this->em->flush();
}
protected abstract function sendVerification(User $user): bool;
}
class EmailVerificationUserRegister extends UserRegister
{
protected function sendVerification(User $user): bool
{
$email = $user->getEmail();
return $this->sendEmail($email);
}
private function sendEmail(string $email): bool
{
// omitted: sending email code
}
}
class PhoneVerificationUserRegister extends UserRegister
{
protected function sendVerification(User $user): bool
{
$phone = $user->getPhoneNr();
return $this->sendSms($phone);
}
private function sendSms(string $phone): bool
{
// omitted: sending sms code
}
}
class NoVerificationUserRegister extends UserRegister
{
protected function sendVerification(User $user): bool
{
$user->verified();
return true;
}
}
Above is shown the template method code. Below, the "client" one
class Client
{
private $register;
public function __construct(UserRegister $register)
{
$this->register = $register;
}
public function register(User $user)
{
// do something before registration
$this->register->register($user);
// do something after registration
}
}
$client = new Client(new EmailVerificationUserRegister());
$client->register($new User());
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).
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:
class UserRegister
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
final public function register(User $user, $sendVerification): void
{
$user->enable();
if (!is_callable($sendVerification) {
return;
}
$verificationHasBeenSent = call_user_func_array($sendVerification, [$user]);
if (is_bool($verificationHasBeenSent) && $verificationHasBeenSent) {
return;
}
$this->em->persist($user);
$this->em->flush();
}
}
Talking of closure as callback, a closure is an anonymous function (also called 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
interface UserRegisterInterface
{
public function register(User $user): void;
}
class EmailVerificationUserRegister implements UserRegisterInterface
{
private $register;
public function __construct(UserRegister $register)
{
$this->register = $register;
}
public function register(User $user)
{
$this->register->register($user, [$this, 'sendVerification']);
}
private function sendVerification(User $user): bool
{
$email = $user->getEmail();
return $this->sendEmail($email);
}
private function sendEmail(string $email): bool
{
// omitted: sending email code
}
}
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
class UserRegister
{
[...]
final public function register(User $user, \Closure $sendVerification): void
{
$user->enable();
$verificationHasBeenSent = $sendVerification($user);
if (is_bool($verificationHasBeenSent) && $verificationHasBeenSent) {
return;
}
[...]
}
}
class EmailVerificationUserRegister implements UserRegisterInterface
{
[...]
public function register(User $user)
{
$this->register->register($user, function (User $user) { return $this->sendVerification($user); });
}
[...]
}
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:
interface VerificationStrategy
{
public function sendVerification(User $user): bool;
}
class UserRegister
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
final public function register(User $user, VerificationStrategy $strategy): void
{
$user->enable();
if (!$strategy->sendVerification($user)) {
return;
}
$this->em->persist($user);
$this->em->flush();
}
}
class EmailVerificationUserRegister implements VerificationStrategy
{
public function sendVerification(User $user): bool
{
$email = $user->getEmail();
return $this->sendEmail($email);
}
private function sendEmail(string $email): bool
{
// omitted: sending email code
}
}
class Client
{
private $register;
public function __construct(UserRegister $register)
{
$this->register = $register;
}
public function register(User $user, VerificationStrategy $strategy)
{
// do something before registration
$this->register->register($user, $strategy);
// do something after registration
}
}
$client = new Client();
$client->register(new User(), new EmailVerificationUserRegister());
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! 🙂
19