Tutorials

Design Patterns - Strategy and Bridge

Views: 45799
Rating: 5/5
Votes: 9

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:

  1. Strategy objects (BasicEmailAdressValidator, LooseRfc2822AdressValidator) - Objects that encapsulate the different algorithms.
  2. 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));
	}          
}

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.

  1. Abstraction (Mail) - Common behaviour and properties of a concept
  2. Refined Abstraction (NotificationMail, SubscriptionMail) - Variations of a concept
  3. Implementor (MailTransport) - Abstraction of a part of the implementation concerning a specific concept
  4. 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());

By abstracting out the concept of 'email transport', we can allow concrete implementations of Mail to vary.