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:

  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.

view plaincopy to clipboard
  1. /**
  2. * Interface for validation strategies
  3. *
  4. */
  5. interface EmailValidationStrategy
  6. {
  7. /**
  8. * Check if the value is valid
  9. *
  10. * @param mixed $value
  11. */
  12. public function isValid($value);
  13. }
  14. /**
  15. * Objects representing an email
  16. *
  17. */
  18. class Mail
  19. {
  20. /**
  21. * Validation strategy
  22. *
  23. * @var EmailValidationStrategy
  24. */
  25. private $_strategy;
  26. /**
  27. * Constructor
  28. *
  29. * @param EmailValidationStrategy $strategy
  30. */
  31. public function __construct(EmailValidationStrategy $strategy)
  32. {
  33. $this->_strategy = $strategy;
  34. }
  35. /**
  36. * Check if the value is valid
  37. *
  38. * @param mixed $mixed
  39. * @return bool
  40. */
  41. public function send($email)
  42. {
  43. //Delegate validation to the strategy object
  44. if($this->_strategy->isValid($email))
  45. {
  46. //Send email
  47. }
  48. else
  49. {
  50. //throw exception
  51. }
  52. }
  53. }
  54. class BasicEmailAdressValidator implements EmailValidationStrategy
  55. {
  56. /**
  57. * Check if the value is valid
  58. *
  59. * @param string $str
  60. * @return bool
  61. */
  62. public function isValid($str)
  63. {
  64. return (is_string($str) && preg_match(“/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i”, $str));
  65. }
  66. }
  67. class LooseRfc2822AdressValidator implements EmailValidationStrategy
  68. {
  69. /**
  70. * Check if the value is valid
  71. *
  72. * @param string $str
  73. * @return bool
  74. */
  75. public function isValid($str)
  76. {
  77. 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));
  78. }
  79. }

Usage:

view plaincopy to clipboard
  1. $mailer = new Mail(new BasicEmailAdressValidator);
  2. $mailer->send($email);

or

view plaincopy to clipboard
  1. $mailer = new Mail(new LooseRfc2822AdressValidator);
  2. $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
view plaincopy to clipboard
  1. /**
  2. * Interface for mailer implementations
  3. *
  4. */
  5. interface MailTransport
  6. {
  7. /**
  8. * Send email
  9. *
  10. */
  11. public function send($from, $to, $body);
  12. }
  13. class SendMailTransport implements MailTransport
  14. {
  15. public function send($from, $to, $body)
  16. {
  17. //send email using sendmail
  18. }
  19. }
  20. class SmtpTransport implements MailTransport
  21. {
  22. public function send($from, $to, $body)
  23. {
  24. //send email using SMTP
  25. }
  26. }
  27. /**
  28. * Objects representing an email
  29. *
  30. */
  31. abstract class Mail
  32. {
  33. /**
  34. * Mail transport implementation
  35. *
  36. * @var MailerTransport
  37. */
  38. private $_transport;
  39. /**
  40. * Email body
  41. *
  42. * @var string
  43. */
  44. private $_body;
  45. /**
  46. * Recipient
  47. *
  48. * @var string
  49. */
  50. private $_to;
  51. /**
  52. * Constructor
  53. *
  54. * @param MailerTransport $imp
  55. */
  56. public function __construct(MailerTransport $imp)
  57. {
  58. $this->_transport = $imp;
  59. }
  60. /**
  61. * Get the email body
  62. *
  63. * @param string $string
  64. */
  65. public function getBody()
  66. {
  67. return $this->_body;
  68. }
  69. /**
  70. * Set the email body
  71. *
  72. * @param string $string
  73. */
  74. public function setBody($string)
  75. {
  76. $this->_body = $string;
  77. }
  78. /**
  79. * Set the email recipient
  80. *
  81. * @param string $address
  82. */
  83. public function getTo()
  84. {
  85. return $this->_to;
  86. }
  87. /**
  88. * Set the email recipient
  89. *
  90. * @param string $address
  91. */
  92. public function setTo($address)
  93. {
  94. $this->_to = $address;
  95. }
  96. /**
  97. * Get the sender address
  98. *
  99. * @return string
  100. */
  101. public function getFrom()
  102. {
  103. return $this->_from;
  104. }
  105. /**
  106. * Set the sender address
  107. *
  108. * @param string $address
  109. */
  110. public function setFrom($address)
  111. {
  112. return $this->_from;
  113. }
  114. }
  115. class NotificationMail extends Mail
  116. {
  117. /**
  118. * Check if the value is valid
  119. *
  120. * @param mixed $mixed
  121. * @return bool
  122. */
  123. public function send()
  124. {
  125. if(!$this->_transport->send($this->getFrom(), $this->getTo(), $this->getBody()))
  126. {
  127. //a failed notice is not that much of isssue
  128. trigger_error(‘Failed to send notice’, E_USER_NOTICE);
  129. }
  130. }
  131. /**
  132. * Format the body as a notice
  133. *
  134. * @return string
  135. */
  136. public function getBody()
  137. {
  138. return “Notice: ” . parent::getBody();
  139. }
  140. }
  141. class SubscriptionMail extends Mail
  142. {
  143. /**
  144. * Check if the value is valid
  145. *
  146. * @param mixed $mixed
  147. * @return bool
  148. */
  149. public function send()
  150. {
  151. if(!$this->_transport->send($this->getFrom(), $this->getTo(), $this->getBody()))
  152. {
  153. //if it fails, we should handle that
  154. throw new Exception(‘Failed to send subscription email’);
  155. }
  156. }
  157. }
  158. $mail = new SubscriptionMail(new SmtpTransport());

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

There are no comments yet, add one below.

Leave a Comment