Design Patterns Workshop - AWS

Patterns are formalized best practices that the programmer must implement themselves in the application. Object-oriented design patterns typically sho...

3 downloads 922 Views 3MB Size
Design Patterns Workshop Brandon Savage

Who am I? •

Former Mozillian; teacher, writer, speaker.



Author of Mastering Object Oriented PHP and Practical Design Patterns in PHP.



Instrument rated private pilot.

What is a design pattern?

A design pattern is a general reusable solution to a commonly occurring problem within a given context in software design.

That doesn't seem so hard, right?

...general[ly] reusable solution...

...commonly occurring problem...

Design patterns are abstract!

You're probably using design patterns without knowing it.

What is NOT a design pattern?

A design pattern is not a finished design that can be transformed directly into source or machine code. It is a description or template for how to solve a problem that can be used in many different situations.

One pattern can have 1,000 implementations!

Patterns are formalized best practices that the programmer must implement themselves in the application. Object-oriented design patterns typically show relationships and interactions between classes or objects, without specifying the final application classes or objects that are involved.

Design patterns are often expressed in diagrams, not code.

Code Sample Warning!!! Code is provided to ILLUSTRATE possible solutions, not to show all possible solutions.

Developing a SOLID understanding

Design patterns often implement OO best practices.

S ingle Responsibility O pen/Closed L iskov substitution I nterface Segregation D ependency injection

SOLID helps us develop good object oriented applications.

Single responsibility

The single responsibility principle states that every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. All its services should be narrowly aligned with that responsibility.

One class, one job.

Examples of a job • Database connection handling

• Caching

• Object instantiation

• Routing

• Context management

• Data modeling

...entirely encapsulated...

The object should be able to do the job completely.

Open/Closed

Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

Two kinds of open/ closed principle

Meyer's open/closed principle

Meyer's open/closed principle • Classes, once complete, should not be modified.

• Errors can be fixed, but the API and internals are set

• Inheritance allows for future modifications and extensions

• Subclasses CAN change the interface.

Polymorphic Open/ Closed Principle

Polymorphism is the natural occurrence of a thing in different forms. !

In computer science, this means an object that works in different ways, but implements a common interface and behavior.

Polymorphic Open/ Closed Principle • The INTERFACE defines the object.

• The internals are open for extension, but the interface closed for modification.

• Changing the interface in subclasses changes the object type.

Most developers use the Polymorphic Open/ Closed Principle.

A Caching Interface
A File Cache
!

class File implements Cache {

!

!

public function get($key) { return file_get_contents($this->path . '/' . $key); } public function set($key, $value) { return file_put_contents($this->path . '/' . $key, $value); } public function delete($key) { $path = $this->path . '/' . $key; unset($path); }

! }

// ...other methods here

An APC Cache
!

class APC implements Cache {

!

! ! ! }

public function get($key) { return apc_fetch($key); } public function set($key, $value) { return apc_store($key, $value); public function delete($key) { apc_delete($key); // ...other methods here

Liskov substitution principle

The Liskov substitution principle states that objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. !

E.g. if A is a type of D, then A can replace instances of D.

Program to an interface, not an implementation.

Design by contract

Design by contract is a philosophy which states that designers should define formal, precise and verifiable interface specifications for software components.

A Caching Interface
ALL instances of Cache should be substitutable for one another!

Do not extend the interface (open/closed violation)


!

class BaseClass {

!

!

public function doSomething() { // ... } public function doSomethingElse() { // .. }

}

!

class MyClass extends BaseClass {

!

!

}

!

public function doesSomethingNew() { // ... }

This is wrong!
!

class BaseClass {

!

!

public function doSomething() { // ... } public function doSomethingElse() { // .. }

}

!

class MyClass extends BaseClass {

!

!

}

!

public function doesSomethingNew() { // ... }

Extending the interface makes the subclass a new type.

Type versus class

Class is the name of the class that defines the object.

Type is the way the object behaves, its interface.

Interface segregation

The interface segregation principle states that no object should be forced to depend upon or implement methods that it doesn’t use. !

Smaller interfaces are better.

Interfaces define behavior. Behavior defines responsibility.

The smaller your interface, the better your application.

The Xerox Example

Beware the "God object"

The God Object inteface Imagick implements Iterator { adaptiveBlurImage ($radius, $sigma, $channel = Imagick::CHANNEL_DEFAULT ); adaptiveResizeImage ($columns, $rows, $bestfit = false ); adaptiveSharpenImage ($radius, $sigma, $channel = Imagick::CHANNEL_DEFAULT ); adaptiveThresholdImage ($width, $height, $offset); addImage ($source); addNoiseImage ($noise_type, $channel = Imagick::CHANNEL_DEFAULT ); affineTransformImage (ImagickDraw $matrix); animateImages ($x_server); annotateImage (ImagickDraw $draw_settings, $x, $y, $angle, $text); appendImages ($stack = false); averageImages (); blackThresholdImage ($threshold); blurImage ($radius, $sigma, $channel); borderImage ( $bordercolor, $width, $height); charcoalImage ($radius, $sigma); chopImage ($width, $height, $x, $y); clear (); clipImage (); clipPathImage (string $pathname, $inside); clone (); clutImage ( $lookup_table $channel = Imagick::CHANNEL_DEFAULT ); coalesceImages (); colorFloodfillImage ( $fill, $fuzz, $bordercolor, $x, $y); colorizeImage ( $colorize, $opacity); combineImages ($channelType); commentImage (string $comment);

...it keeps going... compareImageChannels ( $image, $channelType, $metricType); compareImageLayers ($method); compareImages ( $compare, $metric); compositeImage ($composite_object, $composite, $x, $y $channel = Imagick::CHANNEL_ALL ); __construct ( $files); contrastImage ( $sharpen); contrastStretchImage ($black_po, $white_po, $channel = Imagick::CHANNEL_ALL ); convolveImage (array $kernel, $channel = Imagick::CHANNEL_ALL ); cropImage ($width, $height, $x, $y); cropThumbnailImage ($width, $height); current() cycleColormapImage ($displace); decipherImage (string $passphrase); deconstructImages (); deleteImageArtifact (string $artifact); deskewImage ($threshold); despeckleImage (); destroy (); displayImage (string $servername); displayImages (string $servername); distortImage ($method, array $arguments, $bestfit); drawImage (ImagickDraw $draw); edgeImage ($radius); embossImage ($radius, $sigma); encipherImage (string $passphrase);

...and going... enhanceImage (); equalizeImage (); evaluateImage ($op, $constant $channel = Imagick::CHANNEL_ALL ); exportImagePixels ($x, $y, $width, $height, string $map, $STORAGE); extentImage ($width, $height, $x, $y); flattenImages (); flipImage (); floodFillPaImage ( $fill, $fuzz, $target, $x, $y, $invert, $channel = Imagick::CHANNEL_DEFAULT ); flopImage (); frameImage ( $matte_color, $width, $height, $inner_bevel, $outer_bevel); functionImage ($function, array $arguments, $channel = Imagick::CHANNEL_DEFAULT ); fxImage (string $expression $channel = Imagick::CHANNEL_ALL ); gammaImage ($gamma $channel = Imagick::CHANNEL_ALL ); gaussianBlurImage ($radius, $sigma $channel = Imagick::CHANNEL_ALL ); getColorspace (); getCompression (); getCompressionQuality (); getCopyright (); getFilename (); getFont (); getFormat (); getGravity (); getHomeURL (); getImage ();

...and going still... !

getImageAlphaChannel (); getImageArtifact (string $artifact); getImageBackgroundColor (); getImageBlob (); getImageBluePrimary (); getImageBorderColor (); getImageChannelDepth ($channel); getImageChannelDistortion ( $reference, $channel, $metric); getImageChannelDistortions ( $reference, $metric $channel = Imagick::CHANNEL_DEFAULT ); getImageChannelExtrema ($channel); getImageChannelKurtosis ([ $channel = Imagick::CHANNEL_DEFAULT ); getImageChannelMean ($channel); getImageChannelRange ($channel); getImageChannelStatistics (); getImageClipMask (); getImageColormapColor ($index); getImageColors (); getImageColorspace (); getImageCompose (); getImageCompression (); getCompressionQuality (); getImageDelay (); getImageDepth (); getImageDispose ();

Dependency inversion

Many people think of this as the "dependency injection" principle. It's not.

The dependency inversion principle states that high-level modules should not depend on low-level modules. Both should depend on abstractions. Also, abstractions should not depend on details; details should depend on abstractions.

Type hinting

When we type hint, we are relying on a specific object type. !

Even though PHP will let us hint for a class name, we should be hinting for interfaces, not class names.

By relying on abstractions, we free our application from dependency hell.

We also make testing possible!

You DO write tests, don't you?

Service location is not dependency inversion.

NOT dependency inversion.


!

function delete($id) { $profile = Profile::prospect()->find($id);

! !

}

!

if (!$profile) return Rest::notFound(); if ($profile->delete()) { return Rest::okay([]); } else { return Rest::conflict(); }

Design patterns we will cover • • • • • • • •

Decorator

Mediator

Abstract Factory & Builder

Singleton

Adapter & Facade

Chain of Responsibility

Strategy

Observer

The decorator pattern Composition over inheritance.

Inheritance is great. !

But it has limitations.

PHP doesn't support multiple inheritance!

String Decorator
!

interface Printer { public function printString(); }

!

class StringPrinter implements Printer { protected $string; public function __construct($string) { $this->string = $string; } public function printString() { print($this->string); } }

Strips HTML
!

class StripTagsPrinter implements Printer { protected $printer; public function __construct(Printer $printer) { $this->printer = $printer; } public function printString() { ob_start(); $this->printer->printString(); $string = ob_get_clean(); print strip_tags($string); } }

Makes Text Bold
!

class BoldPrinter implements Printer { protected $printer; public function __construct(Printer $printer) { $this->printer = $printer; } public function printString() { print ''; $this->printer->printString(); print ''; } }

All Together Now
!

$a = new StringPrinter('
hello world'); $a->printString(); //
hello world

!

$a = new StripTagsPrinter(new StringPrinter('
hello world')); $a->printString(); // hello world

!

$a = new BoldPrinter(new StripTagsPrinter(new StringPrinter('
hello world'))); $a->printString(); // hello world

Decorating With Traits

Traits introduced in PHP 5.4

Traits are expressed in objects (they are not part of an object's type)

The Interface and Traits
!

interface Printer { public function printString(); }

!

trait StripTags { public function stripTags() { $this->string = strip_tags($this->string); } }

!

trait BoldString { public function boldString() { $this->string = '' . $this->string . ''; } }

String Printer class PrintString implements Printer { protected $string; public function __construct($string) { $this->string = $string; } public function printString() { print $this->string; } }

Bold String Printer class BoldStringPrinter implements Printer { use BoldString; protected $string; public function __construct($string) { $this->string = $string; } public function printString() { $this->boldString(); print $this->string; } }

Strip Tags/Bold Printer class BoldNoHtmlStringPrinter implements Printer { use StripTags, BoldString; protected $string; public function __construct($string) { $this->string = $string; } public function printString() { $this->stripTags(); $this->boldString(); print $this->string; } }

All Together Now $a = new PrintString('
hello world'); $a->printString(); //
hello world

!

$a = new BoldStringPrinter('
hello world'); $a->printString(); //
hello world

!

$a = new BoldNoHtmlStringPrinter('
hello world'); $a->printString(); // hello world

OCP/LSP Violation?

OCP/LSP Violation?
!

class BoldPrinter implements Printer { protected $printer; public function __construct(Printer $printer) { $this->printer = $printer; } public function printString() { print ''; $this->printer->printString(); print ''; } }

Magic methods are not "part of the interface."

The mediator pattern An object between friends

Problem: you have too many objects.

Many objects talking to each other introduces tight coupling.

Coupling is a measure of the interdependency between objects in a particular application. !

Tight coupling exists when an object is dependent on many other objects, which are in turn dependent upon it. Tight coupling reduces testability and reusability while increasing the cost of maintenance.

Solution: the Mediator

The Mediator Pattern Mediator Object 1

Object 2

Domain Model Gateway Value Obj

Storage

The Mediator Pattern
!

class ValueObject { public function setValues(array $values = array()) { // do stuff } }

!

class DataStoreObject { public function storeValues(array $values = array()) { // do stuff } }

The Mediator Pattern
!

class GatewayObject { public function save() { $values = $this->valueObj->getValues(); return $this->dataObj->storeValues($values); } }

The Mediator Pattern is great for loose coupling, because it discourages modules from talking to one another.

Abstract Factory and Builder Abstracting object creation

Creating objects is a responsibility.

To abstract the object creation responsibility, we use patterns.

Abstract Factory Simple objects, part of a family, with limited configuration

The client only cares about two interfaces: CacheFactory and Cache.

Abstract factories can only produce simple objects.

Abstract factories always return the object on creation.

The Builder Pattern Managing a more complex creation process

Some objects are more complex and require more effort to construct.

Just like Abstract Factory, the Client only knows two interfaces.

The client manages the building process

The created object is only returned when the Client asks for it.

The Builder Pattern
!

class Controller {

!

public function account() { if(!$this->request->getUser()->isAuthenticated()) { $this->response->setRedirect('/'); return $this->response->render(); } $this->response->setTemplate('account.html'); $this->response->setTitle('My Account'); return $this->response->render(); }

}

The Builder Pattern
!

interface AbstractResponseBuilder {

!

public function setRedirect($url); public function setTemplate($template); public function setTitle($title); public function render();

!

}

The Builder Pattern class ResponseBuilder implements AbstractResponseBuilder{

! !

protected $product; public function setRedirect($url) { $this->product = new 301RedirectResponse($url); } public function setTemplate($template) { if(!($this->product instanceof 200OKResponse)) { $this->product = new 200OKResponse; } $this->product->setTemplate($template); } public function setTitle($title) { if(!($this->product instanceof 200OKResponse)) { $this->product = new 200OKResponse; } $this->product->setTitle($title); } public function render() { if(!is_object($this->product)) { throw new InvalidResponseException(); } return $this->product;

}

}

Using these patterns together

Together again
!

interface AbstractORM {

!

public function getInstance();

!

}

! !

class MySQLOrmFactory implements AbstractORM {

!

}

public function getInsatnce() { return new MySQLORM(); }

Together again class PostgresOrmFactory implements AbstractORM {

!

public function getInstance() { return new PostgresORM(); }

}

!

interface ORMBuilder {

!

public public public public public public

function function function function function function

// and so on...

!

}

setTable($table); setColumns(array $columns = array()); setWhere(array $where = array()); setOrderBy($orderBy, $ascDesc); setLimit($limit, start); addJoin($rawSQL);

The Singleton Pattern The pattern we love to hate

A Singleton is a single instance of a particular object within a particular runtime.

The Singleton
!

class MySingleton {

! ! ! !

private static $instance; private function __construct() {} private function __clone() {} public static function getInstance() { if (!(self::$instance instanceof MySingleton)) { self::$instance = new MySingleton(); } return self::$instance; }

!

}

The Singleton and SOLID

The Singleton and SOLID
!

class MySingleton {

! ! ! !

private static $instance; private function __construct() {} private function __clone() {} public static function getInstance() { if (!(self::$instance instanceof MySingleton)) { self::$instance = new MySingleton(); } return self::$instance; }

!

}

The Singleton and Testing

The Singleton and Testing
!

class MySingleton {

! ! ! !

private static $instance; private function __construct() {} private function __clone() {} public static function getInstance() { if (!(self::$instance instanceof MySingleton)) { self::$instance = new MySingleton(); } return self::$instance; }

!

}

The Singleton has uses! !

They're just few and far between.

The Adapter and the Facade Patterns Two similar patterns, two very different reasons

Problem: we don't write all the code we use.

A lot of times, very similar components look very different.

The Adapter Pattern Making two different interfaces look the same

(You've probably implemented this before)

A Caching Interface
A File Cache
!

class File implements Cache {

!

!

public function get($key) { return file_get_contents($this->path . '/' . $key); } public function set($key, $value) { return file_put_contents($this->path . '/' . $key, $value); } public function delete($key) { $path = $this->path . '/' . $key; unset($path); }

! }

// ...other methods here

An APC Cache
!

class APC implements Cache {

!

! ! ! }

public function get($key) { return apc_fetch($key); } public function set($key, $value) { return apc_store($key, $value); public function delete($key) { apc_delete($key); // ...other methods here

We didn't design the caches. But we can design the interface!

The Adapter Pattern and LSP

The Adapter Pattern is NOT the Mediator Pattern!

Mediators mediate between colleagues.

Adapters translate between interfaces.

The Facade Pattern Complex subsystem, simple interface

Problem: you have a complex subsystem that a higher level system must access.

Facades often tie together unrelated subcomponents.

Facades create an interface where one didn't exist before.

WARNING: Laravel “Facades” are not implementing this pattern and are poorly named.

Violation of SRP

How is a Facade NOT An Adapter?!

Adapters create a common interface for SIMILAR objects. !

Facades create interfaces for UNRELATED objects.

Some patterns look similar, but are very different based on intent.

The Chain of Responsibility Pattern Handling things in order

Problem: you need to be able to handle tasks or filters in a particular order.

Each object in the chain either handles the request, or passes it along.

The Chain Interface
!

abstract class Handler { protected $successor; public function appointSuccessor(Handler $successor) { $this->successor = $successor; }

!

protected function passMessage(EmailMessage $message) { if($successor instanceof Handler) { return $this->successor->handleMessage($message); } } abstract public function handleMessage( EmailMessage $message);

}

Wait, you used an abstract class, not an interface!

Abstract Classes ARE Interfaces.

The Chain Interface
!

abstract class Handler { protected $successor; public function appointSuccessor(Handler $successor) { $this->successor = $successor; }

!

protected function passMessage(EmailMessage $message) { if($successor instanceof Handler) { return $this->successor->handleMessage($message); } } abstract public function handleMessage( EmailMessage $message);

}

The Chain Interface
!

abstract class Handler { protected $successor; public function appointSuccessor(Handler $successor) { $this->successor = $successor; }

!

protected function passMessage(EmailMessage $message) { if($successor instanceof Handler) { return $this->successor->handleMessage($message); } } abstract public function handleMessage( EmailMessage $message);

}

The End Of The Chain; Now What Do We Do?

Falling off the chain is bad. Usually.

Option 1: Raise An Exception.

The Chain Interface
!

abstract class Handler { protected $successor; public function appointSuccessor(Handler $successor) { $this->successor = $successor; }

!

protected function passMessage(EmailMessage $message) { if($successor instanceof Handler) { return $this->successor->handleMessage($message); } throw new HandlerNotFoundException( 'I could not find a handler.'); } abstract public function handleMessage( EmailMessage $message);

}

Option 2: Raise a 404 (in a router context)

Option 3: Fall Off (Failure By Design)

Falling off is okay, if that's expected.

Construction of the chain is crucial!

E.g. a greedy regex can keep the chain from working right.

The Strategy Pattern Determining your approach at runtime

Problem: Depending on context, we may want to switch how we execute.

We also know that composition is better than inheritance.

The Strategy Pattern


!

interface Strategy {

! !

}

public function execute($a, $b);

The Strategy Pattern class Add implements Strategy {

!

public function execute($a, $b) { return $a + $b; }

}

!

class Subtract implements Strategy {

!

public function execute($a, $b) { return $a - $b; }

}

!

class Multiply implements Strategy {

!

}

public function execute($a, $b) { return $a * $b; }

The Strategy Pattern
!

class Context {

! !

protected $strategy; public function __construct(Strategy $strategy) { $this->strategy = $strategy; } public function executeStrategy($a, $b) { return $this->strategy->execute($a, $b); }

!

}

The Strategy Pattern relies on a common interface.

The Strategy Pattern


!

interface Strategy {

! !

}

public function execute($a, $b);

The Strategy Pattern makes use of aggregation, rather than inheritance.

The Observer Pattern Watching other objects

Problem: one object should change when another changes state.

An Observer Interface
!

interface Subject {

!

public function registerObserver(Observer $subscriber); public function unregisterObserver(Observer $subscriber);

!

public function notifyObservers($message);

!

}

!

interface Observer { public function notify($message);

!

}

Avoiding a Push/Pull System

A push system: the Subject determines what the Observer wants.

This requires the Subject to know the Observer.

A pull system: the Subject notifies the Observer of a change, and the Observer interrogates the Subject for details.

This requires the Observer to know the Subject, and have a reference to it.

Subjects should tell Observers EVERYTHING.

Observers decide what they care about.

Subjects hold references to Observers.

Take your object oriented programming to the next level. A brand new book on design patterns, with examples written in PHP for web developers!

!

10% off with code VERONA http://www.practicaldesignpatternsinphp.com/

Questions? • [email protected]

• www.brandonsavage.net

• @brandonsavage