The 23 Design Patterns you must know

Based on a famous book I will try to simplify and give examples of the 23 famous design patterns that can help your daily work as developer.

If you do not know the book I am talking about, it is Design Patterns: Elements of Reusable Object-Oriented Software. Even nowadays the Book is still handy and we realize how timeless is its content.

Before we start

So what are Design Patterns?

Yes, Design Patterns are everywhere we need to do a recurring action.

Besides construction, medicine, engineering, we have patterns also for teaching people, cooking (recipes), social interactions (greet, thanks, apologize), selling, designing packings and many many others areas.

Basically, for any repeating task, by experience, the specialists found approaches that work better on each situation and then it becomes a pattern.

Along the history developers have solved similar problems with different solutions. It turned out that to solve common problems, some solutions provided a better result and made the code more ReFlexMain, which is Reusable, Flexible and Maintainable, the holy trinity.

As an Architect, Software Engineer or Developer, Design patterns will give to you the Foundation to solve more complex design problems.

Lets explore them!

Creational Patterns

Define patterns to be used when creating or cloning objects.

Singleton

Singleton actually became an anti-patterns, because it brings many restrictions due the fact is is a single instance of an object, thus a global state in the whole application. It makes system more complex and hard to test.

For a matter of information, I will show the pattern here just for you to don not use it.

Goals of the pattern:

Basically we make impossible to create an instance externally by making a private constructor and then we create a public static method to cache and retrieve the single global state of this class.

Example in PHP:

<?php
class MySingleton
{
    private $instance;

    private function __construct(){}

    public static function getInstance(): MySingleton 
    {
        if (null === $this->instance) {
            $this->instance = new self();
        }

        return $this->instance;
    }
}

Factory Object

As in the real world, the purpose of this patterns is also to create objects.

Benefits:

Example in Java:

public class PackageFactory
{
    public IPackage create(PackageType type)
    {
        IPackage package;

        if (type.is('carrier')) {
            package = new CarrierPackage();
        } else if (type.is('post')) {
            package = new PostPackage();
        } else {
            package = new OrdinaryPackage();
        }

        return package;
    }
}

// Using the factory object
public class Shipping
{
    private PackageFactory packageFactory;

    public Shipping(PackageFactory packageFactory)
    {
        this.packageFactory = packageFactory;
    }

    public void dispatch(PackageType type, Item item)
    {
        IPackage package;

        package = this.packageFactory.create(type);
        package.addItem(item);
        package.close();
        //... other implementation...
    }
}

Factory method

As the factory object, this pattern also creates object, but in a separate method of specialized classes instead.

We can do this making the previously client of the factory an abstract class, so the subclasses will be specialized and responsible to decide which type of object to create.

Benefits:

public abstract class Shipping
{
    public void dispatch(PackageType type, Item item)
    {
        IPackage package;

        package = this.createPackage(type);
        package.addItem(item);
        package.close();
        //... other implementation...
    }

    abstract IPackage createPackage(PackageType type);
}

public class CarrierShipping extends Shipping
{
    IPackage createPackage(PackageType type)
    {
        return new CarrierPackage();
    }
}

public class PostShipping extends Shipping
{
    IPackage createPackage(PackageType type)
    {
        return new PostPackage();
    }
}

public class OrdinaryShipping extends Shipping
{
    IPackage createPackage(PackageType type)
    {
        return new OrdinaryPackage();
    }
}

Structural Patterns

These patterns describe how objects are connected to each other:

Facade

A client of a subsystem don’t want (and don’t need) to know the details of how it works internally. It just wants to request something in order to get something.

A Facade is:

Key principles to use a Facade

When to use Facade?

Example in PHP:

<?php
interface IAccount
{
    public function getBalance(): float;
    public function withdraw(float $amount): void;
    public function deposit(float $amount): void;
}

class InvestmentAccount implements IAccount {}

class SavingAccount implements IAccount {}

class CurrentAccount implements IAccount {}

class BankFacade
{
    public function accountBalance(string $accountNumber): float
    {
        $account = $this->accountRepository->find(accountNumber);

        return $account->getBalance();
    }

    public function accountWithdraw(string $accountNumber, float $amount): void
    {
        $account = $this->accountRepository->find(accountNumber);

        $account->withdraw($amount);
    }

    public function accountDeposit(string $accountNumber, float $amount): void
    {
        $account = $this->accountRepository->find(accountNumber);

        $account->deposit($amount);
    }

    public function createAccount(string $type, string $customerName): Account
    {
        if ($type == 'saving') {
            $account = new SavingAccount($customerName);
        } elseif ($type == 'investment') {
            $account = new InvestmentAccount($customerName);
        } elseif ($type == 'current') {
            $account = new CurrentAccount($customerName);
        }

        $this->accountRepository->save(accountNumber);

        return $account;
    }
}

/**
 * The client is not aware about internal behavior or implementation of subsystems. 
 * It just know about the exposed functionality.
 */
class ApplicationClient
{
    private $bankFacade;

    public function __construct(BankFacade $bankFacade)
    {
        $this->bankFacade = $bankFacade;
    }

    public function operation(string $operation, array $parameters)
    {
        if ($operation === 'new-account') {
            return $this->bankFacade->createAccount($parameters['accountType'], $parameters['customerName']);
        }

        if ($operation === 'deposit') {
            return $this->bankFacade->accountDeposit($parameters['accountNumber'], $parameters['amount']);
        }

        if ($operation === 'withdraw') {
            return $this->bankFacade->accountWithdraw($parameters['accountNumber'], $parameters['amount']);
        }

        if ($operation === 'balance') {
            return $this->bankFacade->accountBalance($parameters['accountType']);
        }
    }
}

Adapter

The output of a system might not comply with the expected input of another system. It requires an adapter to transform this output to an accepted input to the other system.

Involved parts to use the Adapter pattern:

Example in Java:

// This is the "Target interface"
public interface IWsRequester
{
    public Response request(Order order);
}

// This is the "Adapter"
public class WsAdapter implements IWsRequester
{
    //WebService is a third-party library class which we cannot change...
    private WebService adaptee;

    //...constructor

    public Response request(Order order)
    {
        String json = this.orderToJson(order);

        return this.adaptee.request(json);
    }

    private String orderToJson(Order order)
    {
        //... Adapts / translates Order object to JSON string
    }
}

// This is the "Client"
public class WsClient
{
    private IWsRequest requester;

    //...constructor

    public void dispatchOrder(String orderNumber)
    {
        Order order = this.orderRepository.getOrder(orderNumber);

        Response response = this.requester.request(order);

        if (response.code === 200) {
            System.out.println('Success!');
        } else {
            System.out.println('Error!');
        }
    }
}

public class Main
{
    public static void main(String args[])
    {
        WebService adaptee = new WebService("http://external-system-url");
        WsAdapter adapter = new WsAdapter(adaptee);

        WsClient client = new WsClient(adapter);

        //Note that the client does not need to be aware of the "adapteeo" interface thanks to the adapter...
        client.dispatchOrder(args[0]);
    }
}

Composite

Objectives:

Main components of the composite pattern:

UML class diagram composition pattern
UML class diagram composition pattern

Example in Java

import java.util.ArrayList;

/* [Program.java] */
public class Program {
	public static void main(String args[]) {
	
	Playlist studyPlaylist = new Playlist("Study");		
    Playlist rockPlaylist = new Playlist("Rock");
    
	Song rockSong1 = new Song("Nothing else matters");
    Song rockSong2 = new Song("Sultans of swing");
    
	rockPlaylist.add(rockSong1);
    rockPlaylist.add(rockSong2);
    
    Song studySong1 = new Song("Design Patterns");
    Song studySong2 = new Song("Software Architecture");

    studyPlaylist.setPlaybackSpeed(0.25f);
    studyPlaylist.add(studySong1);
    studyPlaylist.add(studySong2);
	studyPlaylist.add(rockPlaylist);
	studyPlaylist.play();
}

public interface IComponent 
{
    public void play();
    public void setPlaybackSpeed(float speed);
    public String getName();
}

// Component class
public class Playlist implements IComponent 
{
	public String playlistName;
	public ArrayList<IComponent> playlist = new ArrayList();

    public Playlist(String playlistName) 
    {
		this.playlistName = playlistName;
    }
    
    public void play()
    {
        playlist.get(0).play();
    }

    public void setPlaybackSpeed(float speed)
    {
        for (IComponent song : playlist) {
            song.setPlaybackSpeed(speed);
        }
    }

    public String getName()
    {
        return playlistName;
    }

    public void add(IComponent component)
    {
        playlist.add(component);
    }

    public void remove(IComponent component)
    {
        playlist.remove(component);
    }
}

// Leaf class
public class Song implements IComponent 
{
	public String songName;
	public float speed = 1;

    public Song(String songName) 
    {
		this.songName = songName;
    }
    
    public void play()
    {
        System.out.println("Playing music ...");
    }

    public void setPlaybackSpeed(float speed)
    {
        this.speed = speed;
    }

    public String getSongName()
    {
        return songName;
    }
}

Proxy

Why to use a proxy?

UML proxy pattern
UML proxy pattern
<?php

// The Subject interface
interface StockInterface
{
    public function processOrder(Order $order): void
}

// The class that will be Proxied. It does not check stock quantity...
class DistributionCenter implements StockInterface
{
    /** @var Stock[] **/
    private $stocks;

    public function processOrder(Order $order): void
    {
        foreach ($order->getItems() as $item) {
            $stock = $this->getStock($item->getProduct());
            $stock->decrease($item->getQuantity());
        }
    }

    public function getStock(Product $product): Stock
    {
        foreach ($this->stocks as $stock) {
            if ($stock->belongsTo($product)) {
                return $stock;
            }
        }
    }
}

// The proxy checks all distribution centers for a stock, otherwise raise an exception
class DistributionCenterProxy implements StockInterface
{
    /** @var DistributionCenter **/
    private $distributionCenters;

    public function processOrder(Order $order): void
    {
        foreach ($this->distributionCenters as $distributionCenter) {
            $hasStock = true;

            foreach ($order->getItems() as $item) {
                $stock = $distributionCenter->getStock($item->getProduct());

                if (!$stock->hasStock($item->getQuantity())) {
                    $hasStock = false;

                    break;
                }
            }

            if ($hasStock) {
                $distributionCenter->processOrder($order);

                return;
            }
        }

        throw new Exception('Insufficient stock');
    }
}

Decorator

Sometimes we need to add some extra feature or behavior for a class, but we do not want to change the original class in order to preserve its individual purpose. The Decorator pattern uses aggregation to combine behaviors to a class at runtime!

Goals:

UML decorator pattern
UML decorator pattern
// The component interface
public interface IWebPage
{
    public void display();
}

// The concrete component
public class BasicWebPage implements IWebPage
{
    public void display()
    {
        // Output page content...
    }
}

// The abstract Decorator
public abstract class WebPageDecorator implements IWebPage
{
    protected IWebPage webPage;

    public WebPageDecorator(IWebPage webPage)
    {
        this.webPage = webPage;
    }

    public void display()
    {
        this.webPage.display();
    }
}

// A concrete decorator. It does not aggregate any other IWebPage component
public class AuthenticationWebPage extends WebPageDecorator
{
    //constructor...

    public void display()
    {
        super.display();
        this.authenticateUser();
    }

    private void authenticateUser()
    {
        //Display authentication form
    }
}

// A concrete decorator. It does not aggregate any other IWebPage component
public class AuthorizationWebPage extends WebPageDecorator
{
    //constructor...

    public void display()
    {
        super.display();
        this.authorizeUser();
    }

    private void authorizeUser()
    {
        //Display authorized user content
    }
}

public class Main
{
    public static void main(String args)
    {
        // Here is we can check the decorator adding behavior in stack
        IWebPage basicPage = new BasicWebPage();
        IWebPage authorizationPage = new AuthorizationWebPage(basicPage);
        IWebPage authenticationPage = new AuthenticationWebPage(authorizationPage);
        
        authenticationPage.display();
    }
}

Behavioral Patterns

Define how independent object work towards a common goal.

Template Method

When you want to establish the steps or behavior of some algorithm and allow customization only for part of it, we call it Template Method.

UML template method pattern
UML template method pattern

Example in PHP:

<?php

abstract class Pizza
{
    public function prepare(): void
    {
        $this->addCover();
        $this->addSauce();
        $this->bake();
    }

    private function bake(): void
    {
        // Common Baking logic
    }

    public abstract function addCover(): void;
    public abstract function addSauce(): void;    
}

class PizzaMozzarella extends Pizza
{
    public function addCover(): void
    {
        // Add mozzarella cheese
    }

    public function addSauce()
    {
        // add olives and oregano
    }
}

class PizzaCarbonara extends Pizza
{
    public function addCover(): void
    {
        // Add parmesan, mozzarella, eggs and bacon
    }

    public function addSauce()
    {
        // add oregano
    }    
}

Chain of Responsibility

When we crate a chain of objects working together to handle requests, we call it a Chain of Responsibility.

Generally they are a series of Handlers objects, each one specialized on handle some part of the requests.

While the request is not satisfied by a handler, they will forward the request to the next handler in the chain.

UML Chain of responsibility pattern
UML Chain of responsibility pattern
<?php

abstract class RequestHandler
{
    /**
     * @var Handler
     */
    protected $nextHandler;

    public function setNextHandler(Handler $nextHandler): self
    {
        $this->nextHandler = $nextHandler;

        return $this;
    }

    public abstract function handle(Request $request): Response;
}

class UserAuthorizerHandler extends RequestHandler
{
    public function handle(Request $request): Response
    {
        if (!$request->getAttribute('authorized')) {
            // Sends user to authorization...
            return new Response();
        }

        return $next->handle($request, $response);
    }
}

class UrlRedirectHandler extends RequestHandler
{
    public function handle(Request $request): Response
    {
        if ($request->getUrl() === 'redirect') {
            // Validate url and create redirect response...
            return new Response();
        }

        return $next->handle($request, $response);
    }
}

class XssValidationHandler extends RequestHandler
{
    public function handle(Request $request): Response
    {
        if ($request->getQueryParam('search')) {
            // If the parameter values corresponds to a XSS attack, block user...
            return new Response();
        }

        return $next->handle($request, $response);
    }
}

// Last handler, if everything went fine...
class LoadViewHandler extends RequestHandler
{
    public function handle(Request $request): Response
    {
        // Loads HTML page by request...
        return new Response();
    }
}

// ...request handling on index.php

$handler1 = new UserAuthorizerHandler();
$handler2 = new UrlRedirectHandler();
$handler3 = new XssValidationHandler();
$handler4 = new LoadViewHandler();

$handler1->setNextHandler($handler2)
    ->setNextHandler($handler3)
    ->setNextHandler($handler4);

return $handler1->handle($request);

State pattern

The state pattern is used when you want to change the behavior off an object depending on its current state at run-time.

Lets see the example in Java. Instead of have lots of conditions inside the ATM class, we distribute the responsibilities to each possible “state” of the ATM machine.

class Atm
{
    private float balance;
    private AtmState currentState;
    private NoFundsState noFundsState;
    private HasFundsState hasFundsState;
    private IdleState idleState;

    public Atm()
    {
        idleState = new IdleState(this);
        noFundsState = new NoFundsState(this);
        hasFundsState = new HasFundsState(this);
        currentState = idleState;
    }

    public void withdraw(float amount)
    {
        currentState.withdraw(amount);
    }

    public void deposit(float amount)
    {
        currentState.deposit(amount);
    }

    public float getBalance()
    {
        return balance;
    }

    public void addMoney(float money)
    {
        this.balance += money;
    }

    public void removeMoney(float money)
    {
        this.balance -= money;
    }

    public void setCurrentState(AtmState currentState)
    {
        this.currentState = currentState;
    }

    public HasFundsState getHasFundsState()
    {
        return this.hasFundsState;
    }

    public NoFundsState getNoFundsState()
    {
        return this.noFundsState;
    }

    public IdleState getIdleState()
    {
        return this.idleState;
    }
}

abstract class AtmState
{
    protected Atm atm;

    public AtmState(Atm atm)
    {
        this.atm = atm;
    }

    public abstract void withdraw(float amount);
    public abstract void deposit(float amount);
}

class NoFundsState extends AtmState
{
    public void withdraw(float amount)
    {
        throw new NoFundsException("No funds");
    }

    public void deposit(float amount)
    {
        super.atm.addMoney(amount);
        super.atm.setState(super.atm.getHasFundsState());
    }
}

class HasFundsState extends AtmState
{
    public void withdraw(float amount)
    {
        if (super.atm.getBalance() < amount) {
            throw new NoFundsException("No funds");
        }

        super.atm.removeMoney(amount);

        if (super.atm.getBalance() <= 0) {
            super.atm.setState(super.atm.getNoFundsState());
        }
    }

    public void deposit(float amount)
    {
        super.atm.addMoney(amount);
    }
}

class IdleState extends AtmState
{
    public void withdraw(float amount)
    {
        if (super.atm.getBalance() < amount) {
            throw new NoFundsException("No funds");
        }
            
        super.atm.removeMoney(amount);

        if (super.atm.getBalance() <= 0) {
            super.atm.setState(super.atm.getNoFundsState());
        } else {
            super.atm.setState(super.atm.getHasFundsState());
        }
    }

    public void deposit(float amount)
    {
        super.atm.addMoney(amount);
        super.atm.setState(super.atm.getHasFundsState());
    }
}

Command

The command encapsulates a request in an object its own. So this command must be forward to a Worker which knows how to execute this command.

(Sender) -Creates-> (Command) -Calls_Method-> (Receiver);

Objects involved in the Command pattern.

Undo/Redo or Memento using Command pattern

Sometimes you need to keep track of all the state changes of an object, so you can easily move back and forth to a program state. A real world example is the undo/redo of text editors. So every time you do a change in the editor you put a command at the top of a stack.

This pattern can be used in conjunction with the Command pattern

UML Command and Memento pattern
UML Command and Memento pattern

Se the example in PHP integrating Memento with Command pattern:

<?php 
interface Receiver
{
    public function execute(array $data);
}

class EraseTextReceiver implements Receiver
{
    public function execute(array $data): void
    {
        //Erase from file: $data['file'], strlen($data['text']), $data['position'];
    }
}

class WriteTextReceiver implements Receiver
{
    public function execute(array $data): void
    { 
        //Write in the file: $data['file'], $data['text'], $data['position'];
    }
}

class SaveFileReceiver implements Receiver
{
    public function execute(array $data): void
    {
        //Save file contents from $data['file']
    }
}

abstract class Command
{
    abstract public function do(): void;
    abstract public function undo(): void;
    abstract public function isReversible(): bool;
}

class WriteCommand extends Command
{
    private $writeReceiver;
    private $eraseFileReceiver;
    private $data;

    public function __construct(
        WriteFileReceiver $writeReceiver, 
        EraseFileReceiver $eraseFileReceiver,
        array $data
    ) {
        $this->writeReceiver = $writeReceiver;
        $this->eraseReceiver = $eraseFileReceiver;
        $this->data = $data;
    }

    public function do(): void
    {
        $this->writeReceiver->execute($this->data);
    }
    
    public function undo(): void
    {
        $this->eraseReceiver->execute($this->data);
    }

    public function isReversible(): bool
    {
        return true;
    }
}

class SaveCommand extends Command
{
    private $data;

    public function __construct(array $data) 
    {
        $this->data = $data;
    }

    public function do(array $data): void
    {
        // Save file: $this->data['file'];
    }
    public function undo(array $data): void
    {
         // cannot undo save
    }

    public function isReversible(): bool
    {
        return false;
    }
}

/**
 * Manage command execution, queue, etc
 * 
 * Here we are using "Memento pattern" where we have two queues "history" and "undoHistory".
 */
class CommandManager
{
    private $history = []; //Queue for doing commands
    private $undoHistory = []; //Queue for undoing commands

    public function doCommand(Command $command)
    {
        $this->history[] = $command;

        $command->do();
    }

    public function undoCommand()
    {
        $last = count($this->history) - 1;

        if (!isset($this->history[$last])) {
            return;
        }

        $lastCommand = $this->history[$last];

        unset($this->history[$last]);

        $this->undoHistory[] = $lastCommand;

        if ($lastCommand->isReversible()) {
            $lastCommand->undo();
        }
    }

    public function redoCommand()
    {
        $last = count($this->undoHistory) - 1;

        if (!isset($this->undoHistory[$last])) {
            return;
        }

        $lastCommand = $this->undoHistory[$last];

        unset($this->undoHistory[$last]);

        $this->history[] = $lastCommand;

        $lastCommand->do();
    }
}

// Interacts with command manager and commands
class Invoker
{
    /**
     * @var CommandManager
     */
    private $commandManager;

    public function invoke()
    {
        $file = new File();
        $writeTextReceiver = new WriteTextReceiver();
        $eraseTextReceiver = new EraseTextReceiver();
        $saveReceiver = new SaveFileReceiver();

        $command1 = new WriteCommand(
            $writeTextReceiver, 
            $eraseTextReceiver, 
            [
                'file' => $file,
                'text' => 'Hello Word', 
                'position' => 0,
            ]
            
        );
        $command2 = new WriteCommand(
            $writeTextReceiver, 
            $eraseTextReceiver, 
            [
                'file' => $file,
                'text' => 'Another text', 
                'position' => 9,
            ]
        );
        $command3 = new SaveCommand(
            [
                'file' => $file,
            ]
        );

        /**
         * Here we can check memento working with Command pattern
         */
        $this->commandManager->doCommand($command1);
        $this->commandManager->doCommand($command2);
        $this->commandManager->doCommand($command3);

        $this->commandManager->undoCommand(); // Cannot undo save
        $this->commandManager->undoCommand(); // Will remove "Another text" from file

        $this->commandManager->redoCommand(); // Will add again "Another text" to file

        $this->commandManager->undoCommand(); // Will remove "Another text" from file
        $this->commandManager->undoCommand(); // Will remove "Hello World" from file
    }
}

Mediator

When we need some objects to interact each other based on some events, sometimes create an direct relation could be difficult to maintain. As these objects can grow in different directions, we do not want them to being coupled.

Imagine that you have a Smart House where you object can take different actions based on some events or other objects states. For instance, when your house temperature is too cold, the Air Conditioning should increase temperature. Or when you enter at home after a day of work, a coffee should be prepared. If it is Sunday morning 9AM, turn on the TV with the News.

It would be really difficult to maintain those objects aware about each other states. There comes handy the Mediator to do the job.

UML Mediator pattern
UML Mediator pattern

In this pattern we call these objects that talk to the Mediator, Colleagues. The Colleagues can notify the Mediator through the Observer Pattern. So, for instance when some relevant event happens with the Thermostat, like “Temperature changed”, the Mediator is notified.

Mediator UML class diagram
Mediator UML class diagram
<?php
abstract class Mediator implements SplObserver {}

abstract class Colleague implements SplSubject
{
    public function attach(SplObserver $observer)
    {
        // Attach Mediator
    }

    public function detach(SplObserver $observer)
    {
        // Detach Mediator
    }

    public function notify()
    {
        //Call Mediator::update
    }
}

class HouseMediator extends Mediator
{  
    public function update(SplSubject $publisher)
    {
        // Check the subject. If is Temperature update by thermostat, change the air conditioning
    }
}

class Thermostat extends Colleague
{
    public function updateCurrentTemperature(float $temperature): void
    {
        // Notify mediator
        $this->notify();
    }
}

class AirConditioning extends Colleague
{
    public function changeTemperature(float $temperature): void
    {
        // Change air conditioning temperature accordingly
    }
}

Observer

When we need to keep objects “observing” changes on other objects (subjects) we use the Observer pattern. This pattern contains main classes:

Observer UML class diagram
Observer UML class diagram

Example in Java:

public interface Subject 
{
    public void addObserver(Observer observer);
    public void removeObserver(Observer observer);
    public void notifyObservers();
}

public interface Observer 
{
	public void update(Subject subject);
}

public class Shipping implements Subject 
{
    private ArrayList<Observer> observers;
    private String productId;
    private String status;

    public void changeShippingStatus(String productId, String status)
    {
        this.productId = productId;
        this.status = status;

        this.notifyObservers();
    }

    public String getProductId()
    {
        return this.productId;
    }

    public String getStatus()
    {
        return this.status;
    }
    
    public void addObserver(Observer observer)
    {
        if (!this.observers.contains(observer)) {
            this.observers.add(observer);    
        }
    }
    
    public void removeObserver(Observer observer)
    {
        if (this.observers.contains(observer)) {
            this.observers.remove(observer);
        }
    }
    
    public void notifyObservers()
    {
        for (Observer observer : this.observers) {
            observer.update(this);
        }
    }
}

public class Customer implements Observer
{
    private String name;
    
    public Customer(String name)
    {
        this.name = name;
    }
    
    public void update(Subject subject)
    {
        System.out.println("Product " + subject.getProductId() + " shipping status is now " + subject.getStatus() + "!");
    }
}

Other patterns

Model View Controller (MVC)

Example:

class Controller
{
    private $model;
    private $view;

    public function __construct(ProductModel $model, ProductView $view)
    {
        $this->model = $model;
        $this->view  = $view;
    }

    public function listProducts(Request $request)
    {
        $products = $this->model->getProducts(
            [
                'searchTerm' => $request->getQueryParam('term')
            ]
            
        );

        $this->view->render($products);
    }
}