Subscribe to PHP Freaks RSS

Design Patterns - Strategy and Bridge

Print
by John Kleijn on Oct 9, 2008 9:51:16 AM - 45,297 views

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.

Comments

S N Nov 29, 2008 11:27:54 PM

True true

Josh Robison Dec 2, 2008 8:35:11 PM

Good stuff.

Nous Dec 5, 2008 1:18:30 AM

thank you..

Nabeel Jan 2, 2009 7:02:45 PM

Interesting example, tho IMO overkill for an 'emailer' class. Explains the concept well though.

boha Jan 25, 2009 1:18:44 PM

Thank you. Very nice tutorial.

huyi Feb 9, 2009 12:21:28 AM

Thank you. Good job.

In Bridge Pattern Demo, the class 'Mail' lacks a attribute (or field, member),
private $_from;

P-H-STEVE Jun 9, 2009 11:20:36 PM

everything's good except when I was reading the code I noticed a small problem...

# /**
# * 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;
# }

get and set are the same lol, they both return the sender...

second one should be

# public function setFrom($address)
# {
# $this->_from = $address;
# }

;) Not that it matters since this is just an example to teach this concept, but it did have me confused for a split second until I realized it was just a small mistake

Good read!

sunwukung Jul 14, 2009 3:10:11 PM

Also - I'm guessing the object type hint MailerTransport is a typo - should be MailTransport to match the interface name?

DarkSuperHero Apr 23, 2010 12:30:51 AM

Great Stuff! A clean and simple explanation of the Bridge and Strategy patterns, I was having a difficult time grasping it. I understand now.

Add Comment

Login or register to post a comment.