October 20th, 2020
Design Patterns – Strategy and Bridge
Introduction
The Strategy and Bridge patterns provide solutions to apply
polymorphism in more flexible way than you can accomplish with only
inheritance.
The patterns are almost identical, differing mostly in intent only.
Though it is this difference that create implementation variations
targeting the respective problems.
Problem
Strategy
How to use different algorithms in a flexible, polymorphic way.
Bridge
How to structurally abstract a varying concept and it’s implementations, in a way that doesn’t break encapsulation.
Solution
The key to successfully implementing the Strategy pattern is designing
a common interface that is flexible enough to support a range of
algorithms. If you can accomplish that, implementation is a snap. It
really is one of the easiest patterns. It’s really just a basic
polymorphic solution.
Let’s start by defining the named handles for the parties involved in this pattern:
- Strategy objects (BasicEmailAdressValidator, LooseRfc2822AdressValidator) – Objects that encapsulate the different algorithms.
- Context object (Mail) – Object dynamically using any algorithm.
We’ll
use a fictional Mailer class, which validates the email adress before
attempting to send it. Email validation can be done in many different
ways, so we encapsulate the validation algorithms in different Strategy
Objects.
- /**
- * Interface for validation strategies
- *
- */
- interface EmailValidationStrategy
- {
- /**
- * Check if the value is valid
- *
- * @param mixed $value
- */
- public function isValid($value);
- }
- /**
- * Objects representing an email
- *
- */
- class Mail
- {
- /**
- * Validation strategy
- *
- * @var EmailValidationStrategy
- */
- private $_strategy;
- /**
- * Constructor
- *
- * @param EmailValidationStrategy $strategy
- */
- public function __construct(EmailValidationStrategy $strategy)
- {
- $this->_strategy = $strategy;
- }
- /**
- * Check if the value is valid
- *
- * @param mixed $mixed
- * @return bool
- */
- public function send($email)
- {
- //Delegate validation to the strategy object
- if($this->_strategy->isValid($email))
- {
- //Send email
- }
- else
- {
- //throw exception
- }
- }
- }
- class BasicEmailAdressValidator implements EmailValidationStrategy
- {
- /**
- * Check if the value is valid
- *
- * @param string $str
- * @return bool
- */
- public function isValid($str)
- {
- return (is_string($str) && preg_match(“/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i”, $str));
- }
- }
- class LooseRfc2822AdressValidator implements EmailValidationStrategy
- {
- /**
- * Check if the value is valid
- *
- * @param string $str
- * @return bool
- */
- public function isValid($str)
- {
- return (is_string($str) && preg_match(“/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+(?:[A-Z]{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)\b/i”, $str));
- }
- }
* Interface for validation strategies * */
interface EmailValidationStrategy { /** * Check if the value is valid *
* @param mixed $value */ public function isValid($value);
}
/** * Objects representing an email * */
class Mail
{ /** * Validation strategy * * @var EmailValidationStrategy */ private
$_strategy; /** * Constructor * * @param EmailValidationStrategy
$strategy */ public function __construct(EmailValidationStrategy
$strategy) { $this->_strategy = $strategy; } /** * Check if the
value is valid * * @param mixed $mixed * @return bool */ public
function send($email) { //Delegate validation to the strategy object
if($this->_strategy->isValid($email)) { //Send email } else {
//throw exception } }
}
class BasicEmailAdressValidator implements EmailValidationStrategy {
/** * Check if the value is valid * * @param string $str * @return bool
*/ public function isValid($str) { return (is_string($str) &&
preg_match(”/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i”, $str)); } }
class LooseRfc2822AdressValidator implements EmailValidationStrategy {
/** * Check if the value is valid * * @param string $str * @return bool
*/ public function isValid($str) { return (is_string($str) &&
preg_match(”/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+(?:[A-Z]{2}|com|org|net|gov|mil|biz|info|mobi|name|aero|jobs|museum)\b/i”,
$str)); } }
Usage:
- $mailer = new Mail(new BasicEmailAdressValidator);
- $mailer->send($email);
or
- $mailer = new Mail(new LooseRfc2822AdressValidator);
- $mailer->send($email);
The
algorithms are nicely encapsulated, and we can add different types of
validation, without having to mess with the Mailer class.
The Bridge pattern something very similar, though it’s focusus on
structure, not behaviour. Keeping the Mail example, a typical example
of Bridge that stresses the structural implications would demonstrate
that the context and implementation can vary independently.
- Abstraction (Mail) – Common behaviour and properties of a concept
- Refined Abstraction (NotificationMail, SubscriptionMail) – Variations of a concept
- Implementor (MailTransport) – Abstraction of a part of the implementation concerning a specific concept
- Concrete Implementor (SendMailTransport, SmtpTransport) – Concrete implementations of a specific concept
- /**
- * Interface for mailer implementations
- *
- */
- interface MailTransport
- {
- /**
- * Send email
- *
- */
- public function send($from, $to, $body);
- }
- class SendMailTransport implements MailTransport
- {
- public function send($from, $to, $body)
- {
- //send email using sendmail
- }
- }
- class SmtpTransport implements MailTransport
- {
- public function send($from, $to, $body)
- {
- //send email using SMTP
- }
- }
- /**
- * Objects representing an email
- *
- */
- abstract class Mail
- {
- /**
- * Mail transport implementation
- *
- * @var MailerTransport
- */
- private $_transport;
- /**
- * Email body
- *
- * @var string
- */
- private $_body;
- /**
- * Recipient
- *
- * @var string
- */
- private $_to;
- /**
- * Constructor
- *
- * @param MailerTransport $imp
- */
- public function __construct(MailerTransport $imp)
- {
- $this->_transport = $imp;
- }
- /**
- * Get the email body
- *
- * @param string $string
- */
- public function getBody()
- {
- return $this->_body;
- }
- /**
- * Set the email body
- *
- * @param string $string
- */
- public function setBody($string)
- {
- $this->_body = $string;
- }
- /**
- * Set the email recipient
- *
- * @param string $address
- */
- public function getTo()
- {
- return $this->_to;
- }
- /**
- * Set the email recipient
- *
- * @param string $address
- */
- public function setTo($address)
- {
- $this->_to = $address;
- }
- /**
- * Get the sender address
- *
- * @return string
- */
- public function getFrom()
- {
- return $this->_from;
- }
- /**
- * Set the sender address
- *
- * @param string $address
- */
- public function setFrom($address)
- {
- return $this->_from;
- }
- }
- class NotificationMail extends Mail
- {
- /**
- * Check if the value is valid
- *
- * @param mixed $mixed
- * @return bool
- */
- public function send()
- {
- if(!$this->_transport->send($this->getFrom(), $this->getTo(), $this->getBody()))
- {
- //a failed notice is not that much of isssue
- trigger_error(‘Failed to send notice’, E_USER_NOTICE);
- }
- }
- /**
- * Format the body as a notice
- *
- * @return string
- */
- public function getBody()
- {
- return “Notice: ” . parent::getBody();
- }
- }
- class SubscriptionMail extends Mail
- {
- /**
- * Check if the value is valid
- *
- * @param mixed $mixed
- * @return bool
- */
- public function send()
- {
- if(!$this->_transport->send($this->getFrom(), $this->getTo(), $this->getBody()))
- {
- //if it fails, we should handle that
- throw new Exception(‘Failed to send subscription email’);
- }
- }
- }
- $mail = new SubscriptionMail(new SmtpTransport());
class SendMailTransport implements MailTransport
{
public function send($from, $to, $body)
{
//send email using sendmail
}
}
class SmtpTransport implements MailTransport
{
public function send($from, $to, $body)
{
//send email using SMTP
}
}
/**
* Objects representing an email
*
*/
abstract class Mail
{
/**
* Mail transport implementation
*
* @var MailerTransport
*/
private $_transport;
/**
* Email body
*
* @var string
*/
private $_body;
/**
* Recipient
*
* @var string
*/
private $_to;
/**
* Constructor
*
* @param MailerTransport $imp
*/
public function __construct(MailerTransport $imp)
{
$this->_transport = $imp;
}
/**
* Get the email body
*
* @param string $string
*/
public function getBody()
{
return $this->_body;
}
/**
* Set the email body
*
* @param string $string
*/
public function setBody($string)
{
$this->_body = $string;
}
/**
* Set the email recipient
*
* @param string $address
*/
public function getTo()
{
return $this->_to;
}
/**
* Set the email recipient
*
* @param string $address
*/
public function setTo($address)
{
$this->_to = $address;
}
/**
* Get the sender address
*
* @return string
*/
public function getFrom()
{
return $this->_from;
}
/**
* Set the sender address
*
* @param string $address
*/
public function setFrom($address)
{
return $this->_from;
}
}
class NotificationMail extends Mail
{
/**
* Check if the value is valid
*
* @param mixed $mixed
* @return bool
*/
public function send()
{
if(!$this->_transport->send($this->getFrom(), $this->getTo(), $this->getBody()))
{
//a failed notice is not that much of isssue
trigger_error(’Failed to send notice’, E_USER_NOTICE);
}
}
/**
* Format the body as a notice
*
* @return string
*/
public function getBody()
{
return “Notice: ” . parent::getBody();
}
}
class SubscriptionMail extends Mail
{
/**
* Check if the value is valid
*
* @param mixed $mixed
* @return bool
*/
public function send()
{
if(!$this->_transport->send($this->getFrom(), $this->getTo(), $this->getBody()))
{
//if it fails, we should handle that
throw new Exception(’Failed to send subscription email’);
}
}
}
$mail = new SubscriptionMail(new SmtpTransport());
By abstracting out the concept of ‘email transport’, we can allow concrete implementations of Mail to vary.