This Behavioral Design Pattern is really useful and solves one of most common problems in OOP that is “do things decoupled”, is related to the O of Solid Principles – Open to extension but closed for modification.
If you programming to an interface doing composition instead of inheritance, you’re probably already using it frequently.
This is one of my favorites patterns, because is simple and also one of more important patterns to help you to keep things clear, separating responsibilities and grow your system tidy.
Intent
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from the clients that use it.
Diagram
Our problem and solution
Let’s consider an example.
Well, first imagine that you have a kind of really simple Documents System where you have document title and description.
You work in a really well oriented documentation where your boss say that you need to display this document in HTML and it will never change. You could just need a simple DocumentDisplay class that returns your HTML representation of your document. However, in few months a huge client of your company needs a new display, and now, the document should be displayed in XML.
If your DocumentDisplay class are not using this pattern, defining a family of algorithms, is very probably that you will add an IF statement to solve this questions. The main thing in this concept is, foreach new Algorithm(Presenter for your Document) you will need to break SOLID Principles and will have to made changes in this class.
We will solve this problem adding Strategy Design Pattern to this DocumentDisplay.
To representing this, let’s gets started with some code. Our project Structure should be like this:
.designpatterns
.. src
... Behavioral
.... Strategy
..... DocumentSystem
.. tests
... Behavioral
.... DocumentSystem
As you already know and is a professional developer, you’re always working with interfaces, our first file should be our Document Interface that I will call just Document (Could be whatever you like, for instance IDocument, DocumentInterface, DocumentContract, etc).
Document.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\Strategy\DocumentSystem;
interface Document
{
public function getTitle(): string;
public function getDescription(): string;
}
Ok, now we need a concrete representation of this, here I will call just DocumentConcrete.
DocumentConcrete.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\Strategy\DocumentSystem;
class DocumentConcrete implements Document
{
private string $title;
private string $description;
public function __construct(string $title, string $description)
{
$this->title = $title;
$this->description = $description;
}
public function getTitle(): string
{
return $this->title;
}
public function getDescription(): string
{
return $this->description;
}
}
Now lets observe our possible problem, describe in the first scenario (When this class wont ever change).
Maybe you just needed something like this, on our DocumentDisplay (Don’t create this now)
<?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\Strategy\DocumentSystem;
class DocumentDisplay
{
private Document $document;
public function __construct(Document $document)
{
$this->document = $document;
}
public function display(Document $document)
{
return <<<EOF
<html>
<head>
<title>{$document->getTitle()}</title>
</head>
<body>
{$document->getDescription()}
</body>
</html>
EOF;
// Here you could have some ifs to decide kind of presenter that you have
// If you're using this approach your class will be growing forever
// if (presenter === HTML )
// do Something
// if (presenter === XML )
// do Something
// if (presenter === MD )
// do Something
// if (presenter === TEXT )
// do Something
}
}
Now let’s review it, and add a Strategy that I will call Presenter. It is like a contract (Could be an abstract class or interface, it doesn’t matter). Each presenter know how to display a document in your own format.
Presenter.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\Strategy\DocumentSystem;
interface Presenter
{
public function display(Document $document);
}
We need two changes now, first one is adding this interface to our DocumentDisplay and then we need to move the HTML format to a new Class that I will call HTMLPresenter.
DocumentDisplay.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\Strategy\DocumentSystem;
class DocumentDisplay
{
private Document $document;
private Presenter $presenter;
public function __construct(Document $document, Presenter $presenter)
{
$this->document = $document;
$this->presenter = $presenter;
}
public function display()
{
return $this->presenter->display($this->document);
}
}
Observe that the algorithm to display will be now in our Concrete Implementations of our Presenter Strategy.
Lets create our Presenters:
HTMLPresenter.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\Strategy\DocumentSystem;
class HTMLPresenter implements Presenter
{
public function display(Document $document)
{
return <<<EOF
<html>
<head>
<title>{$document->getTitle()}</title>
</head>
<body>
{$document->getDescription()}
</body>
</html>
EOF;
}
}
XMLPresenter.php
<?php
declare(strict_types=1);
namespace DesignPatterns\Behavioral\Strategy\DocumentSystem;
class XMLPresenter implements Presenter
{
public function display(Document $document)
{
return <<<EOF
<document>
<title>{$document->getTitle()}</title>
<description>{$document->getDescription()}</description>
</document>
EOF;
}
}
To finish this, let’s create our test to assert that everything is as expected.
DocumentDisplayTest.php
<?php
declare(strict_types=1);
namespace Tests\Behavioral\Strategy\DocumentSystem;
use DesignPatterns\Behavioral\Strategy\DocumentSystem\Document;
use DesignPatterns\Behavioral\Strategy\DocumentSystem\DocumentConcrete;
use DesignPatterns\Behavioral\Strategy\DocumentSystem\DocumentDisplay;
use DesignPatterns\Behavioral\Strategy\DocumentSystem\HTMLPresenter;
use DesignPatterns\Behavioral\Strategy\DocumentSystem\XMLPresenter;
use PHPUnit\Framework\TestCase;
class DocumentDisplayTest extends TestCase
{
public function testShouldPrintInHTMLFormat()
{
$display = new DocumentDisplay(
new DocumentConcrete('My Title', 'Hello World'),
new HTMLPresenter
);
$this->assertEquals(
<<<EOF
<html>
<head>
<title>My Title</title>
</head>
<body>
Hello World
</body>
</html>
EOF,
$display->display()
);
}
public function testShouldPrintInXMLFormat()
{
$display = new DocumentDisplay(
new DocumentConcrete('My Title', 'Hello World'),
new XMLPresenter
);
$this->assertEquals(
<<<EOF
<document>
<title>My Title</title>
<description>Hello World</description>
</document>
EOF,
$display->display()
);
}
}
Diagram representation
You can follow our repository https://github.com/diegosm/designpatterns, everything is there.
I hope you understood this concept and feel free to ask something or do your contribution. There are many examples on internet of this pattern.
This article is a part of our Design Pattern Series, follow our Design Patterns – What, when, why? link 🙂