27
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 likeclass 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:
That’s all.
Happy coding! 🙂
27