Design Patterns

Web Developement

Máté Cserép

April 2018, Budapest

in object oriented

What are design patterns?

How are they created?

Most software today is very much like an Egyptian pyramid with millions of bricks piled on top of each other, with no structural integrity, but just done by brute force and thousands of slaves.

Alan Kay

What are design patterns?

  • Design patterns describes simple and elegant solutions to specific problems.
  • Design patterns capture solutions that have developed and evolved over time.
  • They reflect untold redesign and recoding as developers have struggled for greater reuse and flexibility in their software.
  • Design patterns require neither unusual language features nor amazing programming tricks.
  • All can be implemented in standard languages, though they might take a little more work than ad hoc solutions. The extra effort invariably pays dividends in increased flexibility and reusability.

Design Patterns

The book that started it all (1994).
 

Community refers to authors as the "Gang of Four".

Some figures and text in these slides come from this book.

Singleton

final class Singleton
{
    private static $inst = null;

    public static function getInstance()
    {
        if (self::$inst === null) {
            self::$inst = new Singleton();
        }
        return self::$inst;
    }

    private function __construct()
    { }
}

$obj = Singleton::getInstance();

Ensure a class only has one instance, and provide a global point of access to it.

Singleton

Front Controller of MVC frameworks often implement the Singleton pattern.

Singleton

// Entry point index.php of a ZF application

require_once '../bootstrap.php';

Zend_Controller_Front::getInstance()
    ->setControllerDirectory('modules/default/controllers')
    ->registerPlugin(new Content_Plugin())
    // ...
    ->dispatch();

Front Controller of MVC frameworks often implement the Singleton pattern.

Overusing Singleton is an anti-pattern.

Factory

class DatabaseProvider
{
    public static function create($type)
    { /* ... */ }

}

class MySQLProvider extends DatabaseProvider
{ /* ... */ }

class MSSQLProvider extends DatabaseProvider
{ /* ... */ }


$provider = DatabaseProvider::create('mysql');

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory lets a class defer instantiation to subclasses.

Factory

use Symfony\Component\Form\Forms;

$formFactory = Forms::createFormFactory();
use Symfony\Component\HttpFoundation\Request;

$request = new Request(/* ... */);
$request = Request::create(/* ... */);
$request = Request::createFromGlobals();

Often used in MVC frameworks, e.g. in Symfony:

Builder

Separate the construction of a complex object from its representation so that the same construction process can create different representations.

Builder

Builder

use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\DateType;

class TaskController extends Controller
{
    public function createAction(Request $request)
    {
        $form = $this->createFormBuilder()
            ->add('task', TextType::class)
            ->add('dueDate', DateType::class)
            ->getForm();
    
        return $this->render('task/create.html.twig', array(
            'form' => $form->createView(),
        ));
    }
}
$qb = $repository->createQueryBuilder('p');
$query = $qb
    ->add('where', $qb->expr()->like('p.name', ':name'))
    ->add('orderBy', $qb->expr()->orderBy('p.price', 'ASC'))
    ->setParameter('name', 'computer')
    ->getQuery();

Often used in query building (e.g. Doctrine):

Often used in form building (e.g. Symfony):

Strategy

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Strategy

interface OutputFormatter
{
  public function process($data);
}
class JsonFormatter implements OutputFormatter
{
  public function process($data)
  { /* ...*/ }
}
class XmlFormatter implements OutputFormatter
{
  public function process($data)
  { /* ...*/ }
}
class View
{
  public $formatter;
  public __construct(OutputFormatter $formatter)
  {
    $this->formatter = formatter;
  }

  public function render($data)
  {
    return $this->formatter->process($data);
  }
}
$view = new View(new JsonFormatter());

$data = [ 'name' => 'Máté' ];
$view->render($data);

Example usage: output rendering

Also used in dependency injection containers.

Observer

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Observer

interface IObservable
{
  function addObserver(IObserver $observer);
}

interface IObserver
{
  function onChanged(IObservable $sender, $args);
}

class User
{ /* ... */ }
class UserManager implements IObservable
{
  private $_users = array();
  private $_observers = array();
 
  public function addUser(User $user)
  {
    $this->_users[] = $user;

    foreach($this->_observers as $obs)
      $obs->onChanged($this, $user);
  }
 
  public function addObserver( $observer )
  {
    $this->_observers[] = $observer;
  }
}
$manager = new UserManager();
$manager->addObserver( new UserLogger());
$manager->addUser(new User("Gipsz Jakab"));
class UserLogger implements IObserver
{
  public function onChanged($sender, $args)
  {
    echo "${args->name} user was added" . PHP_EOL;
  }
}
 

Mediator

Define an object that encapsulates how a set of objects interact. Mediator promotes loose coupling by keeping objects from referring to each other explicitly.

Mediator

Mediator

The EventDispatcher system in Symfony also uses the Mediator design pattern.

use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\EventDispatcher\Event;
 
$dispatcher = new EventDispatcher();
 
// add listeners
$dispatcher->addListener('blog.post.saved', function (BlogPostEvent $event) {
    echo 'Updating RSS feed' . PHP_EOL;
});
$dispatcher->addListener('blog.post.saved', function (BlogPostEvent $event) {
    echo 'Sending emails' . PHP_EOL;
});
 
$blogPost = new BlogPost(...);
$manager = new Manager(...);
 
$manager->save($blogPost);
 
// dispatch the event
$event = new BlogPostEvent($blogPost);
$dispatcher->dispatch('blog.post.saved', $event);

Observable

Producer

(Observable)

Consumer

(Observer)

Consumer

(Observer)

Consumer

(Observer)

Mediator

Producer

Consumer

Consumer

Consumer

Mediator

Command

Encapsulate a request as an object, thereby parameterizing clients with different requests, queue or log requests.

Chain of Command

interface ICommand
{
  function execute();
}

class LogCommand implements ICommand
{
  public function execute()
  {
    // Write logs
  }
}

class MailCommand implements ICommand
{
  public function execute()
  {
    // Send email
  }
}
class CommandChain implements ICommand
{
  private $_commands = array();
 
  public function add(ICommand $cmd)
  {
    $this->_commands[] = $cmd;
  }
 
  public function execute( $name, $args )
  {
    foreach($this->_commands as $cmd)
    {
       $cmd->execute();
    }
  }
}

$cmd = new CommandChain();
$cmd->add(new LogCommand());
$cmd->add(new MailCommand());
$cmd->execute();

May embed commands on multiple levels.

Iterator

Access the elements of an aggregate object sequentially without exposing its underlying representation.

CreateIterator is also an example to the Factory design pattern.

Iterator

Often used in ORM frameworks, e.g. with Doctrine:

$context = $this->getDoctrine()->getManager();
$query = $context->createQuery('SELECT ...');
$i = 0;

// implements PHP's native Iterator interface
$iterable = $query->iterate();
foreach ($iterable as $row)
{
    $obj = $row[0];
    // work on object ...

    if ($i % 100 == 0)
    {
        // Executes all updates
        $context->flush();
        // Detaches all objects from Doctrine
        $context->clear();
    }
    ++$i;
}
$context->flush();
$context = $this->getDoctrine()->getManager();
$query = $context->createQuery('SELECT ...');
$i = 0;

$iterable = $query->iterate();
while($iterable->valid())
{
    $row = $iterable->current();
    $obj = $row[0];
    // work on object ...

    if ($i % 100 == 0)
    {
        // Executes all updates
        $context->flush();
        // Detaches all objects from Doctrine
        $context->clear();
    }

    $iterable->next();
    ++$i;
}
$context->flush();

Proxy

Provide a placeholder for another object to control access to it.

Image

ImageProxy

Controller

In memory

In memory

On disk

Proxy

Lazy loading (like in ORM frameworks) also utilizes proxy pattern:

$context = $this->getDoctrine()->getManager();
$products = $context->getRepository(Product::class);

$qb = $products->createQueryBuilder('p');
$query = $qb
    ->add('where',
        $qb->expr()->like('p.name', '?1'))
    ->add('orderBy', 
        $qb->expr()->orderBy('p.price', 'ASC'))
    ->setParameter(1, 'computer')
    ->getQuery();

// $query is a proxy for $result
$result = $query->getResult();
foreach($result as $product)
{
    // ...
}
MyDbContext context = new MyDbContext();
DbSet<Product> products = context.Products;
Product searched = Products.Find(42);

searched.Price = 500;
context.SaveChanges();

IQueryable<Product> query = products
    .Where(p => p.Name.Contains('computer'))
    .OrderBy(p => p.Price);

IList<Product> result = query.ToList();
foreach(Product product in result)
{
    // ...
}

foreach(Product product in query)
{
    // ...
}

PHP with Doctrine

C# with Entity Framework & LINQ

Decorator

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

Often used in front-end visualization frameworks.

Decorator

$view = 
new BorderDecorator(
  new ScrollBarDecorator(
    new TextView()
  )
);

$view->render();

Typical use-case is constructing form fields:

  • adding labels
  • adding error messages
  • adding styling

Dependency injection

Client

<<interface>

ServiceInterface

Service

Injector

  1. A Client layer uses (aggregates) a Service
  2. The ServiceInterface interface is extracted from the Service
  3. The Client now aggregates the interface type
  4. A third component, the Injector injects the concrete implementation into the Client
class FilePersistence
{
    public function __construct($filename)
    { /* ... */ }

    public function load() { /* ... */ }
    public function save() { /* ... */ }
}
class MyController extends Controller
{
    private $_persistence;

    public function __construct()
    {
        $this->_persistence = 
            new FilePersistence("save.dat");
    }

    // actions ...
}

Dependency injection

With this architecture the model (persistence) and the controller layers are tightly coupled.

What happens if we add another implementation of the model layer?

class FilePersistence
{
    public function __construct($filename)
    { /* ... */ }

    public function load() { /* ... */ }
    public function save() { /* ... */ }
}
class MyController extends Controller
{
    private $_persistence;

    public function __construct()
    {
        $this->_persistence = 
            new FilePersistence("save.dat");
    }

    // actions ...
}

Dependency injection

class DatabasePersistence
{
    public function __construct(
        $host, $user, $passwd, $dbname)
    { /* ... */ }

    public function load() { /* ... */ }
    public function save() { /* ... */ }
}

We shall extract an interface and use subtype polymorphism.

interface PersistenceInterface
{
    public function load();
    public function save();
}

class FilePersistence 
    implements PersistenceInterface
{
    public function __construct($filename)
    { /* ... */ }

    public function load() { /* ... */ }
    public function save() { /* ... */ }
}

class DatabasePersistence 
    implements PersistenceInterface
{
    public function __construct(
        $host, $user, $passwd, $dbname)
    { /* ... */ }

    public function load() { /* ... */ }
    public function save() { /* ... */ }
}

Dependency injection

How can we make the chosen implementation dynamically selectable?

class MyController extends Controller
{
    private $_persistence;

    public function __construct()
    {
        // How can we select dynamically, in runtime?
        $this->_persistence = new FilePersistence(
            "save.dat");
        $this->_persistence = new DatabasePersistence(
            "localhost", "loginet", "loginet", "loginet");
    }

    // actions ...
}

Dependency injection

Make the controller and the model layer loosely coupled!

class MyController extends Controller
{
    private $_persistence;

    public function __construct(PersistenceInterface $persistence)
    {
        $this->_persistence = $persistence
    }

    // actions ...
}
  1. We inject the dependent Service (the model)
  2. Into the Client (the controller)
  3. From outside, by an external component, the Injector.

Injection can be constructor-based or setter- based (method)

Inversion of Control

UserController

<<Symfony>>

UserManager

<<Custom Service>>

EntityManager

<<Doctrine>>

Direction of aggregation

Direction of dependency injection

The flow of control is inversed, therefore we name it

Inversion of Control (IoC)

  • Can register a type.
  • Can register a mapping of a type to an interface
    (or base class).
  • Can provide an instantiated object for a requested type.
  • Can provide an instantiated object for a requested interface.

IoC containers

Symfony has a bundled IoC container named: Service Container

  • Automatically registers classes based on autoload.
  • Automatically injects objects into constructors.
    Also supports setter-injection.
  • Dependency injection is transitive.
  • Can provide an instantiated object on request

Symfony as an IoC container

use Symfony\Component\HttpFoundation\Request;
use Psr\Log\LoggerInterface;

/**
 * @Route("/test")
 */
public function testAction(Request $request, LoggerInterface $logger)
{
    $logger->info('Wow, I just used the Symfony Service Container!');
}
use Psr\Log\LoggerInterface;

/**
 * @Route("/test")
 */
public function testAction()
{
    $logger = $this->get(LoggerInterface::class);
    $logger->info('Wow, I just used the Symfony Service Container!');
}

Request an object by injection:

Request an object manually:

Symfony as an IoC container

class UserService
{
    private $_repository;

    public function __construct(EntityManagerInterface $entityManager)
    {
        // This only works in a Controller subclass:
        //$this->getDoctrine()->getRepository(User::class);

        // So we fetch the entity mananger via dependency injection!
        $this->_repository = $entityManager->getRepository(User::class);        
    }

    // methods ...
}
class UserController extends Controller
{
    private $_service;

    public function __construct(UserService $service)
    {
        $_service = $service;       
    }

    // actions ...
}

Symfony as an IoC container

class UserService
{
    private $_repository;
    private $_superAdmin;

    public function __construct(EntityManagerInterface $entityManager, $superAdmin)
    {
        $this->_repository = $entityManager->getRepository(User::class);  
        $this->_superAdmin = $superAdmin;      
    }

    // methods ...
}
# config/services.yaml
services:
    # ...

    # explicitly configure the service
    AppBundle\Service\UserService:
        arguments:
            $superAdmin: 'admin@loginet.hu'

How to inject arguments with non-registered types?

Configure service registration:

Symfony as an IoC container

class SomeController extends Controller
{
    private $_persistence;

    public function __construct(PersistenceInterface $persistence)
    {
        $this->_persistence = $persistence;   
    }

    // actions ...
}
# config/services.yaml
services:
    # ...

    # explicitly configure the service
    AppBundle\Service\PersistenceInterface: AppBundle\Service\DatabasePersistence

How to inject interface arguments with
multiple implementations?

Configure service registration:

Symfony as an IoC container

class SomeController extends Controller
{
    private $_persistence;

    public function __construct(
        DatabasePersistence $persistence)
    {
        $this->_persistence = $persistence;   
    }

    // actions ...
}
# config/services.yaml
services:
    # ...

    # explicitly configure the service
    AppBundle\Service\DatabasePersistence:
        factory: [AppBundle\Service\PersistenceFactory, createDatabasePersistence]

How to inject arguments whom creation follows the
factory design pattern?

Configure service registration:

class PersistenceFactory
{
    public function createFilePersistence()
    { /* ... */ }

    public function createDatabasePersistence()
    { /* ... */ }
}

Symfony as an IoC container

class UserService
{
    private $_persistence;
    private $_mailer;

    public function __construct(DatabasePersistence $persistence)
    {
        $this->_persistence = $persistence;   
    }

    public function setMailer(MailerInterface $mailer)
    {
        $this->mailer = $mailer;
    }
}
# config/services.yaml
services:
    # ...

    # explicitly configure the service
    AppBundle\Service\UserService:
        calls:
            - [setMailer, ['@mailer']] // using alias OR
            - [setMailer, [\Swift_Mailer]] // using concrete class

How to inject optional arguments?

Symfony as an IoC container

# config/services.yaml
services:
    # ...

    # explicitly configure the service
    AppBundle\Service\UserService:
        shared: false

What about object lifetime in an IoC container?

  • per object request (non-shared / transient)
  • per web request (shared / scoped)
  • per application (singleton)

In Symfony the default is shared, non-shared is configurable, singleton is not supported.

Design patterns in Web Development

By Cserép Máté

Design patterns in Web Development

Design Patterns, Singleton, Factory, Builder, Strategy, Observer, Mediator, Command, Iterator, Proxy, Decorator, Dependency injection, IoC container

  • 146