How to save a project

using design patterns

Dawid Mazur / @dwdmzr
"Experienced" PHP Developer @ Clearcode

Who am I?

  • I'm a developer
  • Used to team lead / PM
  • Trying to be active in the community
  • Also teaching kids IT

Design patterns again?

Why am I doing this?

Why do I need design patterns?

Because just knowing OOP does not make you a great developer

You have to know how to use it effectively

Pros

  • They make your code SOLID
  • They are battle-tested
  • They make us understand each other better

Ok, that's pretty neat

but how do I learn these?

The story of the
Ms. Grażynka's project

you won't get this in any book

Dawid Mazur / @dwdmzr
¯\_(ツ)_/¯ @ Clearcode


class Invoice {
    public function setStatus(string $status) : void {}
    public function getFullPrice() : float {}
}

class Contract {
    public function calculateInvoice() : Invoice {}
    public function printPdf() : void {}
}
                    

class Contract {
    private function isPaidMonthly() : bool {}
    private function getMonthlyInvoice() : Invoice {}
    private function getFullInvoice() : Invoice {}

    public function calculateInvoice() : Invoice {
        if ($this->isPaidMonthly()) {
            return $this->getMonthlyInvoice();
        }

        return $this->getFullInvoice();
    }

    public function printPdf() : void {}
}
					

class Contract {
    private function isPaidMonthly() : bool {}
    private function getMonthlyInvoice() : Invoice {}
    private function getFullInvoice() : Invoice {}

    public function calculateInvoice() : Invoice {
        if ($this->isPaidMonthly()) {
            return $this->getMonthlyInvoice();
        }

        return $this->getFullInvoice();
    }

    public function printPdf() : void {}
}
					

Strategy

Encapsulating algorithms


interface InvoiceGeneratingStrategy {
    public function generateInvoice() : Invoice;
}
					

class MonthlyInvoiceGeneratingStrategy implements InvoiceGeneratingStrategy {
    public function generateInvoice(): Invoice {}
}

class QuarterlyInvoiceGeneratingStrategy implements InvoiceGeneratingStrategy {
    public function generateInvoice(): Invoice {}
}

class FullInvoiceGeneratingStrategy implements InvoiceGeneratingStrategy {
    public function generateInvoice(): Invoice {}
}
					

class Contract {
    private function isPaidMonthly() : bool {}
    private function getInvoiceGeneratingStrategy() : InvoiceGeneratingStrategy {}

    public function calculateInvoice() : Invoice {
        return $this->getInvoiceGeneratingStrategy()->generateInvoice();
    }

    public function printPdf() : void {}
}
					

class Contract {
    private function isPaidMonthly() : bool {}
    private function getInvoiceGeneratingStrategy() : InvoiceGeneratingStrategy {}

    public function calculateInvoice() : Invoice {
        return $this->getInvoiceGeneratingStrategy()->generateInvoice();
    }

    public function printPdf() : void {}
}
					

Factory Method

Encapsulating creation


abstract class Contract {
    abstract protected function getInvoiceGeneratingStrategy() : InvoiceGeneratingStrategy;

    public function calculateInvoice() : Invoice {
        return $this->getInvoiceGeneratingStrategy()->generateInvoice();
    }

    public function printPdf() : void {}
}
					

class MonthlyPaidContract extends Contract {
    protected function getInvoiceGeneratingStrategy(): InvoiceGeneratingStrategy
    {
        return new MonthlyInvoiceGeneratingStrategy();
    }
}

class FullyPaidContract extends Contract {
    protected function getInvoiceGeneratingStrategy(): InvoiceGeneratingStrategy
    {
        return new FullInvoiceGeneratingStrategy();
    }
}
					

abstract class Contract {
    abstract protected function getInvoiceGeneratingStrategy() : InvoiceGeneratingStrategy;
    abstract protected function getPdfPrintingTemplate() : ContractTemplate;

    public function calculateInvoice() : Invoice {
        return $this->getInvoiceGeneratingStrategy()->generateInvoice();
    }

    public function printPdf() : void {
        $template = $this->getPdfPrintingTemplate();
        // do some printing magic
        // (ノ°∀°)ノ⌒・*:.。. .。.:*・゜゚・*☆
    }
}
					

abstract class Contract {
    abstract protected function getInvoiceGeneratingStrategy() : InvoiceGeneratingStrategy;
    abstract protected function getPdfPrintingTemplate() : ContractTemplate;

    public function calculateInvoice() : Invoice {
        return $this->getInvoiceGeneratingStrategy()->generateInvoice();
    }

    public function printPdf() : void {
        $template = $this->getPdfPrintingTemplate();
        // do some printing magic
        // (ノ°∀°)ノ⌒・*:.。. .。.:*・゜゚・*☆
    }
}
					

class MonthlyPaidContract extends Contract {
    protected function getInvoiceGeneratingStrategy(): InvoiceGeneratingStrategy
    {
        return new MonthlyInvoiceGeneratingStrategy();
    }

    protected function getPdfPrintingTemplate(): ContractTemplate
    {
        return new MonthlyPaidContractTemplate();
    }
}
					

Abstract Factory

Encapsulating creation
of a family of objects


abstract class ContractComponentFactory {
    abstract public function getInvoiceGeneratingStrategy() : InvoiceGeneratingStrategy;
    abstract public function getPdfPrintingTemplate() : ContractTemplate;
}
					

class MonthlyPaidContractComponentFactory extends ContractComponentFactory {
    public function getInvoiceGeneratingStrategy(): InvoiceGeneratingStrategy
    {
        return new MonthlyInvoiceGeneratingStrategy();
    }

    public function getPdfPrintingTemplate(): ContractTemplate
    {
        return new MonthlyPaidContractTemplate();
    }
}
					

class Contract {
    public function __construct(
        ContractComponentFactory $componentFactory
    ) {
        $this->factory = $componentFactory;
    }

    public function calculateInvoice() : Invoice {
        return $this->factory->getInvoiceGeneratingStrategy()->generateInvoice();
    }

    public function printPdf() : void {
        $template = $this->factory->getPdfPrintingTemplate();
        //	(/ ̄ー ̄)/~~☆’.・.・:★’.・.・:☆
    }
}
					

$contract = new Contract(
	new MonthlyPaidContractComponentFactory()
);
					

class OrderContractComponentFactory extends ContractComponentFactory {
    public function getInvoiceGeneratingStrategy(): InvoiceGeneratingStrategy
    {
        return new FullInvoiceGeneratingStrategy();
    }

    public function getPdfPrintingTemplate(): ?ContractTemplate
    {
        return null;
    }
}
					

class Contract {
    (...)

    public function printPdf() : void {
        $template = $this->factory->getPdfPrintingTemplate();

        if ($template) {
            // do some printing magic
            // 	(ノ>ω<)ノ :。・:*:・゚’★,。・:*:・゚’☆
        }
    }
}
					

Null Object

An elegant null replacement


class OrderContractComponentFactory extends ContractComponentFactory {
    public function getInvoiceGeneratingStrategy(): InvoiceGeneratingStrategy
    {
        return new FullInvoiceGeneratingStrategy();
    }

    public function getPdfPrintingTemplate(): ContractTemplate
    {
        return new NullContractTemplate();
    }
}
					

abstract class ContractTemplate {
    abstract public function getPagesToPrint() : array;
}

class NullContractTemplate extends ContractTemplate {
    public function getPagesToPrint() : array {
        return [];
    }
}
					

class Invoice {
    (...)
    public function setStatus(string $status) : void {
        switch ($status) {
            case InvoiceStatus::PAID:
                $this->yayMoreMoneySms($this->getFullPrice());
                break;
        }

        $this->status = $status;
    }
}
					

class Invoice {
    (...)
    public function setStatus(string $status) : void {
        switch ($status) {
            case InvoiceStatus::PAID:
                $this->yayMoreMoneySms($this->getFullPrice());
                break;
            case InvoiceStatus::OVERDUE:
                $this->dispatchMareczek(
                    $this->getFullPrice() * 1.25,
                    $this->getFullAddress()
                );
                break;
        }

        $this->status = $status;
    }
}
					

Observer

Sharing the state change


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

interface Subject {
    public function attach(Observer $observer) : void;
    public function detach(Observer $observer) : void;
    public function notify(Observer $observer) : void;
    // some helper functions
    public function getStatus() : string;
    public function getFullPrice() : float;
    public function getFullAddress() : string;
}
					

class Invoice implements Subject {
    public function attach(Observer $observer) : void {}
    public function detach(Observer $observer) : void {}

    public function notify(Observer $observer) : void {
        $observer->update($this);
    }

    public function setStatus(string $status) : void {
        $this->status = $status;

        foreach ($this->observers as $observer) {
            $this->notify($observer);
        }
    }
}
					

class MareczekObserver implements Observer {
    private function dispatchMareczek(float $howMuch, string $address) {}

    public function update(Subject $subject): void
    {
        if ($subject->getStatus() === InvoiceStatus::OVERDUE) {
            $this->dispatchMareczek(
                $subject->getFullPrice() * 1.25,
                $subject->getFullAddress()
            );
        }
    }
}
					

class MareczekObserver implements Observer {
    private function dispatchMareczek(float $howMuch, string $currency, string $address) {}

    public function update(Subject $subject): void
    {
        if ($subject->getStatus() === InvoiceStatus::OVERDUE) {
            $this->dispatchMareczek(
                $subject->getFullPrice() * 1.25,
                $subject->getCurrency(),
                $subject->getFullAddress()
            );
        }
    }
}
					

Mareczek list

  • 1000 x 1,25 = 1250 PLN
  • 500 x 1,25 = 625 EUR
  • 2000 x 1,25 = 2500 DDDd
  • 999,99 x 1,25 = 1249,9875 z\u0142

Value Object

Encapsulating values as an entity


class Money {
    private $amount;
    private $currency;

    private function validateCurrency(string $currency) : bool {}
    public function multiply(float $multiplier) : Money {}
    public function changeCurrency(string $currency) : Money {}

    public function __construct(int $amount, string $currency)
    {
        $this->validateCurrency($currency);

        $this->amount = $amount;
        $this->currency = $currency;
    }
}
					

class Money {
    private $amount;
    private $currency;

    private function validateCurrency(string $currency) : bool {}
    public function multiply(float $multiplier) : Money {
        return new Money(
            ceil($this->amount * $multiplier),
            $currency
        );
    }
    public function changeCurrency(string $currency) : Money {}
    public function __construct(int $amount, string $currency)
    {
        $this->validateCurrency($currency);
        $this->amount = $amount;
        $this->currency = $currency;
    }
}
					

class MareczekObserver implements Observer {
    private function dispatchMareczek(Money $howMuch, string $address) {}

    public function update(Subject $subject): void
    {
        if ($subject->getStatus() === InvoiceStatus::OVERDUE) {
            $this->dispatchMareczek(
                $subject->getFullPrice()->multiply(1.25),
                $subject->getFullAddress()
            );
        }
    }
}
					

Command

Encapsulating function call


abstract class InvoiceCommand
{
    abstract public function execute();
    abstract public function undo();
    abstract public function getLogData() : string;
    abstract public function serialize() : string;
}
					

class PayInvoiceCommand extends InvoiceCommand
{
    public function execute() {}
    public function undo() {}
    public function getLogData(): string {}
    public function serialize(): string {}
}
					

class DebugPayInvoiceCommand extends PayInvoiceCommand {
    private function debug($data) {}

    public function execute()
    {
        $this->debug($this->data);
        parent::execute();
    }
}
					

class ProfiledPayInvoiceCommand extends PayInvoiceCommand {
    private function startProfiling() {}
    private function stopProfiling() {}

    public function execute()
    {
        $this->startProfiling();
        parent::execute();
        $this->stopProfiling();
    }
}
					

class ProfiledDebugPayInvoiceCommand extends PayInvoiceCommand {
    private function startProfiling() {}
    private function stopProfiling() {}
    private function debug($data) {}

    public function execute()
    {
        $this->debug($this->data);
        $this->startProfiling();
        parent::execute();
        $this->stopProfiling();
    }
}
					

You could call that...

A fractal of bad design

:d

Decorator

Ads functionality dynamically


abstract class CommandDecorator extends InvoiceCommand {
    protected $decoratedCommand;

    function __construct(InvoiceCommand $decoratedCommand)
    {
        $this->decoratedCommand = $decoratedCommand;
    }
}
					

class DebugCommandDecorator extends CommandDecorator {
    private function debug($data) {}

    public function execute()
    {
        $this->debug($this->data);
        $this->decoratedCommand->execute();
    }

    public function undo() {$this->decoratedCommand->undo();}
    public function getLogData(): string {$this->decoratedCommand->getLogData();}
    public function serialize(): string {$this->decoratedCommand->serialize();}
}
					

class ProfileCommandDecorator extends CommandDecorator {
    private function startProfiling() {}
    private function stopProfiling() {}

    public function execute()
    {
        $this->startProfiling();
        $this->decoratedCommand->execute();
        $this->stopProfiling();
    }

    public function undo() {$this->decoratedCommand->undo();}
    public function getLogData(): string {$this->decoratedCommand->getLogData();}
    public function serialize(): string {$this->decoratedCommand->serialize();}
}
					

$command = new DebugCommandDecorator(
    new ProfileCommandDecorator(
        new PayInvoiceCommand()
    )
);
					

Summary

  1. Strategy - for algorithm encapsulation
  2. Factory method - for object creation encapsulation
  3. Abstract factory - for object family creation encapsulation
  4. Null object - gets rid of null, implements interface
  5. Observer - gets notified about Subject state change
  6. Value Object - encapsulation of complex values
  7. Command - encapsulation of a function call
  8. Decorator - add behavior without using inheritance

Cons?

Pattern fever

Which just happens to be the name of my blog
[shameless-self-promotion]
pattern-fever.com
[/shameless-self-promotion]

Thank you for your attention!

Questions?
@dwdmzr    dwdmzr    dwd.mazur


Slides
dawidmazur.eu/phpers-summit


Feedback is very welcome
joind.in/event/phpers-summit-2018


Check out Coding Dojo Silesia!
codingdojosilesia