be pragmatic, be solid (at boiling frogs, wrocław)

130
Be pragmatic, be SOLID Krzysztof Menżyk @kmenzyk

Upload: krzysztof-menzyk

Post on 13-Apr-2017

537 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Be pragmatic,be SOLID

Krzysztof Menżyk @kmenzyk

Page 2: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Krzysztof Menżyk

Technical Leader at practises TDDbelieves that software is a craftloves domain modellingobsessed with homebrewingplays squash

Page 3: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Do you consider yourselfa professional software developer?

Page 4: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

New client

Greenfield project

Starting from scratch

Page 5: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)
Page 6: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

What went wrong?

Page 7: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Your software is bound to change

Page 8: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

What are the symptomsof bad design?

Page 9: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

The design is hard to change.

Rigidity

Page 10: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

The design is easy to break.

Fragility

Page 11: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Immobility

The design is hard to reuse.

Page 12: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

It is easy to do the wrong thing, but hard to do the right thing.

Viscosity

Page 13: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Design stamina hypothesis

time

cumulativefunctionality

design payoff lineno design

good design

by Martin Fowler

Page 14: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Design will pay offif you plan your product to succeed.

Page 15: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

What is Object Oriented Design

then?

Page 16: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)
Page 17: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Design Principles andDesign Patterns

Robert C. Martin

Page 18: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Single Responsibility Principle

Open Closed Principle

Liskov Substitution Principle

Interface Segregation Principle

Dependency Inversion Principle

Page 19: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Single Responsibility Principle

Open Closed Principle

Liskov Substitution Principle

Interface Segregation Principle

Dependency Inversion Principle

Page 20: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

SOLID

Page 21: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

SingleResponsibilityPrinciple

A class should have only one reason to change.

Page 22: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Gather together those things that change for the same reason.

Separate those things that change for different reasons.

Page 23: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

High cohesion between things that change for the same reason.

Loose coupling between things that change for different reasons.

Page 24: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

final class Employee{ public static function hire($name, $forPosition, Money $withSalary) { // ... }

public function promote($toNewPosition, Money $withNewSalary) { // ... }

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

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

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

Hiring policies have changed

Page 25: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

final class Employee{ public static function hire($name, $forPosition, Money $withSalary) { // ... }

public function promote($toNewPosition, Money $withNewSalary) { // ... }

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

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

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

The design of our REST API

has changed

Page 26: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

final class Employee{ public static function hire($name, $forPosition, Money $withSalary) { // ... }

public function promote($toNewPosition, Money $withNewSalary) { // ... }

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

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

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

DB table holdingemployee datahas changed

Page 27: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

What responsibilities the class has?

Page 28: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

final class Employee{ public static function hire($name, $forPosition, Money $withSalary) { // ... }

public function promote($toNewPosition, Money $withNewSalary) { // ... }

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

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

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

HR business logic

Page 29: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

final class Employee{ public static function hire($name, $forPosition, Money $withSalary) { // ... }

public function promote($toNewPosition, Money $withNewSalary) { // ... }

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

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

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

Data presentation(via REST API)

Page 30: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

final class Employee{ public static function hire($name, $forPosition, Money $withSalary) { // ... }

public function promote($toNewPosition, Money $withNewSalary) { // ... }

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

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

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

Persistence

Page 31: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)
Page 32: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

final class Employee{ public static function hire($name, $forPosition, Money $withSalary) { // ... }

public function promote($toNewPosition, Money $withNewSalary) { // ... }

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

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

public function delete() { // ... }} violation

Page 33: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

final class Employee{ public static function hire($name, $forPosition, Money $withSalary) { // ... }

public function promote($toNewPosition, Money $withNewSalary) { // ... }}

class EmployeeSerializer{ public function toJson(Employee $employee) { // ... }}

class EmployeeRepository{ public function save(Employee $employee) { // ... }

public function delete(Employee $employee) { // ... }}

the right

way

Page 34: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

final class Employee{ public static function hire($name, $forPosition, Money $withSalary) { // ... }

public function promote($toNewPosition, Money $withNewSalary) { // ... }}

class EmployeeSerializer{ public function toJson(Employee $employee) { // ... }}

class EmployeeRepository{ public function save(Employee $employee) { // ... }

public function delete(Employee $employee) { // ... }}

High cohesion

Page 35: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

final class Employee{ public static function hire($name, $forPosition, Money $withSalary) { // ... }

public function promote($toNewPosition, Money $withNewSalary) { // ... }}

class EmployeeSerializer{ public function toJson(Employee $employee) { // ... }}

class EmployeeRepository{ public function save(Employee $employee) { // ... }

public function delete(Employee $employee) { // ... }}

Loose coupling

Page 36: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Try to describe the responsibility in a single sentence.

Page 37: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

What about applying SRP to class methods?

Page 38: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

What about applying SRP to test methods?

Page 39: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

/** @test */public function test_employee(){ $employee = Employee::hire('John Doe', 'Junior Developer', Money::EUR(2000));

$this->assertEquals(Money::EUR(2000), $employee->getSalary());

$employee->promote('Senior Developer', Money::EUR(3000));

$this->assertEquals(Money::EUR(3000), $employee->getSalary());

$employee->promote('Technical Leader', Money::EUR(2000));

$this->assertEquals(Money::EUR(3000), $employee->getSalary());}

Page 40: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

/** @test */public function it_hires_with_salary(){ $employee = Employee::hire('John Doe', 'Junior Developer', Money::EUR(2000));

$this->assertEquals(Money::EUR(2000), $employee->getSalary());}

/** @test */public function it_promotes_with_new_salary(){ $employee = Employee::hire('John Doe', 'Junior Developer', Money::EUR(2000)); $employee->promote('Senior Developer', Money::EUR(3000));

$this->assertEquals(Money::EUR(3000), $employee->getSalary());}

/** @test */public function it_does_not_promote_if_new_salary_is_not_bumped(){ $employee = Employee::hire('John Doe', 'Senior Developer', Money::EUR(3000)); $employee->promote('Technical Leader', Money::EUR(2000));

$this->assertEquals(Money::EUR(3000), $employee->getSalary());}

the right

way

Page 41: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

One test covers one behaviour.

Page 42: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

SRP violation usually indicates other violations.

Page 43: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

SOLID

Page 44: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

OpenClosedPrinciple

Software entities should be open for extension, but closed for modification.

Page 45: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Write once, change never!

Page 46: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Wait! What?

Page 47: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class Shortener{ public function shorten(Url $longUrl) { if (!$this->hasHttpScheme($longUrl)) { throw new InvalidUrl('Url has no "http" scheme'); }

// do stuff to shorten valid url

return $shortenedUrl; }

private function hasHttpScheme(Url $longUrl) { // ... }}

/** @test */public function it_does_not_shorten_url_without_http(){ $urlToFtp = // ...

$this->setExpectedException(InvalidUrl::class, 'Url has no "http" scheme');

$this->shortener->shorten($urlToFtp);}

Page 48: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class Shortener{ public function shorten(Url $longUrl) { if (!$this->hasHttpScheme($longUrl)) { throw new InvalidUrl('Url has no "http" scheme'); }

// do stuff to shorten valid url

return $shortenedUrl; }

// ...}

Shorten only PL links

Page 49: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class Shortener{ public function shorten(Url $longUrl) { if (!$this->hasHttpScheme($longUrl)) { throw new InvalidUrl('Url has no "http" scheme'); }

if (!$this->hasPlDomain($longUrl)) { throw new InvalidUrl('Url has no .pl domain'); }

// do stuff to shorten valid url

return $shortenedUrl; }

private function hasPlDomain(Url $longUrl) { // ... }

// ...}

Shorten only PL links

Page 50: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

/** @test */public function it_shortens_only_urls_with_pl_domains(){ $urlWithEuDomain = // ...

$this->setExpectedException(InvalidUrl::class, 'Url has no .pl domain');

$this->shortener->shorten($urlWithEuDomain);}

Page 51: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

/** @test */public function it_shortens_only_urls_with_pl_domains(){ $urlWithEuDomainButWithHttpScheme = // ...

$this->setExpectedException(InvalidUrl::class, 'Url has no .pl domain');

$this->shortener->shorten($urlWithEuDomainButWithHttpScheme);}

Page 52: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

/** @test */public function it_shortens_urls(){ $validUrl = // make sure the url satisfies all "ifs"

$shortenedUrl = $this->shortener->shorten($validUrl);

// assert}

Page 53: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Testing seems hard?

Page 54: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class Shortener{ public function shorten(Url $longUrl) { if (!$this->hasHttpScheme($longUrl)) { throw new InvalidUrl('Url has no "http" scheme'); }

if (!$this->hasPlDomain($longUrl)) { throw new InvalidUrl('Url has no .pl domain'); }

// do stuff to shorten valid url

return $shortenedUrl; }

private function hasPlDomain(Url $longUrl) { // ... }

// ...}

violation

Page 55: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Abstraction is the key.

Page 56: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

interface Rule{ /** * @param Url $url * * @return bool */ public function isSatisfiedBy(Url $url);}

Page 57: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class Shortener{ public function addRule(Rule $rule) { // ... }

public function shorten(Url $longUrl) { if (!$this->satisfiesAllRules($longUrl)) { throw new InvalidUrl(); }

// do stuff to shorten valid url

return $shortenedUrl; }

private function satisfiesAllRules(Url $longUrl) { // ... }}

the right

way

Page 58: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class HasHttp implements Rule{ public function isSatisfiedBy(Url $url) { // ... }}

class HasPlDomain implements Rule{ public function isSatisfiedBy(Url $url) { // ... }}

Page 59: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

”There is a deep synergy between testability and good design.”

– Michael Feathers

Page 60: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

SOLID

Page 61: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

LiskovSubstitutionPrinciple

Subtypes must be substitutable for their base types.

Page 62: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class InMemoryTweets implements Tweets{ protected $tweets = [];

public function add(Tweet $tweet) { $this->tweets[$tweet->id()] = $tweet; }

public function get($tweetId) { if (!isset($this->tweets[$tweetId])) { throw new TweetDoesNotExist(); }

return $this->tweets[$tweetId]; }}

Page 63: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

interface Tweets{ public function add(Tweet $tweet);

public function get($tweetId);}

Page 64: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Public API is not enough.

Page 65: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Design by contract

Page 66: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

PreconditionsWhat does it expect?

Page 67: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

interface Tweets{ public function add(Tweet $tweet);

public function get($tweetId);}

Page 68: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

interface Tweets{ /** * @param Tweet $tweet */ public function add(Tweet $tweet);

/** * @param int $tweetId */ public function get($tweetId);}

Page 69: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

PostconditionsWhat does it guarantee?

Page 70: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

interface Tweets{ /** * @param Tweet $tweet */ public function add(Tweet $tweet);

/** * @param int $tweetId */ public function get($tweetId);}

Page 71: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

interface Tweets{ /** * @param Tweet $tweet */ public function add(Tweet $tweet);

/** * @param int $tweetId * * @return Tweet * * @throws TweetDoesNotExist If a tweet with the given id * has not been added yet */ public function get($tweetId);}

Page 72: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

InvariantsWhat does it maintain?

Page 73: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

interface Tweets{ /** * @param Tweet $tweet */ public function add(Tweet $tweet);

/** * @param int $tweetId * * @return Tweet * * @throws TweetDoesNotExist If a tweet with the given id * has not been added yet */ public function get($tweetId);}

Page 74: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

interface Tweets{ /** * Adds a tweet to the collection * which can be obtained by „get” * @param Tweet $tweet */ public function add(Tweet $tweet);

/** * @param int $tweetId * * @return Tweet * * @throws TweetDoesNotExist If a tweet with the given id * has not been added yet */ public function get($tweetId);}

Page 75: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

LSP in terms of the contract

Page 76: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Preconditions are no stronger than the base class method.

Page 77: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class InMemoryTweets implements Tweets{ // ...

public function get($tweetId) { if ($tweet < 10) { throw new \RuntimeException(); }

// ... }}

violation

Page 78: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Postconditions are no weaker than the base class method.

Page 79: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class InMemoryTweets implements Tweets{ // ...

public function get($tweetId) { // ...

if ($tweet->isArchived()) { return null; } // ... }}

violation

Page 80: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

public function retweet($tweetId){ try { $tweet = $this->tweets->get($tweetId); } catch (TweetDoesNotExist $e) { // ... }

// ...}

Page 81: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class PersistentTweets extends EntityRepository implements Tweets{ public function add(Tweet $tweet) { $this->getEntityManager()->persist($tweet); $this->getEntityManager()->flush(); }

public function get($tweetId) { return $this->find($tweetId); }}

Page 82: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class EntityRepository{ /** * Finds an entity by its primary key / identifier. * * @param mixed $id The identifier. * @param int $lockMode The lock mode. * @param int|null $lockVersion The lock version. * * @return object|null The entity instance * or NULL if the entity can not be found. */ public function find($id, $lockMode = LockMode::NONE, $lockVersion = null) { // ... }

// ...}

Page 83: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

function retweet($tweetId){ if ($this->tweets instanceof PersistentTweets) { $tweet = $this->tweets->get($tweetId);

if (null === $tweet) { // ... } } else { try { $tweet = $this->tweets->get($tweetId); } catch (TweetDoesNotExist $e) { // .. } }

// ...} violation

Page 84: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Violations of LSP arelatent violations of OCP.

Page 85: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class PersistentTweets extends EntityRepository implements Tweets{ // ...

public function get($tweetId) { $tweet = $this->find($tweetId);

if (null === $tweet) { throw new TweetDoesNotExist(); }

return $tweet; }}

the right

way

Page 86: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

LSP violations are difficult to detect until it is too late.

Page 87: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Ensure the expected behaviour of your base class is preserved in

derived classes.

Page 88: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

SOLID

Page 89: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

InterfaceSegregationPrinciple

Clients should not be forced to depend on methods they do not use.

Page 90: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Many client specific interfaces are better than one general purpose

interface.

Page 91: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

interface EventDispatcherInterface{ public function dispatch($eventName, Event $event = null);

public function addListener($eventName, $listener, $priority = 0);

public function removeListener($eventName, $listener);

public function addSubscriber(EventSubscriberInterface $subscriber);

public function removeSubscriber(EventSubscriberInterface $subscriber);

public function getListeners($eventName = null);

public function hasListeners($eventName = null);}

Page 92: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class HttpKernel implements HttpKernelInterface, TerminableInterface{ public function terminate(Request $request, Response $response) { $this->dispatcher->dispatch( KernelEvents::TERMINATE, new PostResponseEvent($this, $request, $response) ); }

private function handleRaw(Request $request, $type = self::MASTER_REQUEST) { // ... $this->dispatcher->dispatch(KernelEvents::REQUEST, $event);

// ...

$this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event);

// ... }

private function filterResponse(Response $response, Request $request, $type) { // ... $this->dispatcher->dispatch(KernelEvents::RESPONSE, $event); // ... }

// ...}

Page 93: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class ImmutableEventDispatcher implements EventDispatcherInterface{ public function dispatch($eventName, Event $event = null) { return $this->dispatcher->dispatch($eventName, $event); }

public function addListener($eventName, $listener, $priority = 0) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); }

public function addSubscriber(EventSubscriberInterface $subscriber) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); }

public function removeListener($eventName, $listener) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); }

public function removeSubscriber(EventSubscriberInterface $subscriber) { throw new \BadMethodCallException('Unmodifiable event dispatchers must not be modified.'); }

// ...}

violation

Page 94: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

It serves too many different types of clients.

Page 95: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Design interfaces from clients point of view.

Page 96: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

interface EventDispatcherInterface{ public function dispatch($eventName, Event $event = null);}

interface EventListenersInterface{ public function addListener($eventName, $listener, $priority = 0); public function removeListener($eventName, $listener);}

interface EventSubscribersInterface{ public function addSubscriber(EventSubscriberInterface $subscriber); public function removeSubscriber(EventSubscriberInterface $subscriber);}

interface DebugEventListenersInterface{ public function getListeners($eventName = null); public function hasListeners($eventName = null);} the right

way

Page 97: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class EventDispatcher implements EventDispatcherInterface, EventListenersInterface, EventSubscribersInterface{ // ...}

class DebugEventDispatcher extends EventDispatcher implements DebugEventListenersInterface{ // ...}

Page 98: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

SOLID

Page 99: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

DependencyInversionPrinciple

High-level modules should not depend on low-level modules, both should depend on abstractions.

Abstractions should not depend on details. Details should depend on abstractions.

Page 100: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class PayForOrder{ private $payPalApi;

public function __construct(PayPalApi $payPalApi) { $this->payPalApi = $payPalApi; }

public function function pay(Order $order) { // ...

$token = $this->payPalApi->createMethodToken($order->creditCard()); $this->payPalApi->charge($token, $order->amount());

// ... }}

Page 101: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

PayForOrder

PayPalApi

Business Layer

Integration Layer

Page 102: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

High-level module

Low-level module

PayForOrder

PayPalApi

violation

Page 103: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Abstraction is the key.

Page 104: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

PaymentApi

PayPalApi

PayForOrder

Page 105: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class PayForOrder{ public function __construct(PaymentApi $paymentApi) { $this->paymentApi = $paymentApi; }

public function function pay(Order $order) { // ...

$token = $this->paymentApi->createMethodToken($order->creditCard()); $this->paymentApi->createTransaction($token, $order->amount());

// ... }}

Page 106: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class PayForOrder{ public function __construct(PaymentApi $paymentApi) { $this->paymentApi = $paymentApi; }

public function function pay(Order $order) { // ...

$token = $this->paymentApi->createMethodToken($order->creditCard()); $this->paymentApi->createTransaction($token, $order->amount());

// ... }}

Abstractions should not depend on details.

Page 107: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class PayForOrder{ public function __construct(PaymentProvider $paymentProvider) { $this->paymentProvider = $paymentProvider; }

public function function pay(Order $order) { // ...

$this->paymentProvider->charge($order->creditCard(), $order->amount());

// ... }}

the right

way

Details should depend on abstractions.

Page 108: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

interface PaymentProvider{ public function charge(CreditCard $creditCard, Money $forAmount);}

class PayPalApi implements PaymentProvider{ public function charge(CreditCard $creditCard, Money $forAmount) { // ... }}

Page 109: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Define the interface from the usage point of view.

Page 110: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Interface should communicate responsibilities rather than

implementation details.

Page 111: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Concrete things change alot.

Abstract things change muchless frequently.

Page 112: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

PaymentApi

PayPalApiexternal library

PayForOrder

Page 113: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

PaymentApi

PayPalProvider

PayForOrder

PayPalApiexternal library

Page 114: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class PayPalProvider extends PayPalApi implements PaymentProvider{ public function charge(CreditCard $creditCard, Money $forAmount) { $token = $this->createMethodToken($creditCard); $this->createTransaction($token, $forAmount); }}

Page 115: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class PayPalProvider implements PaymentProvider{ public function __construct(PayPalApi $payPal) { $this->payPal = $payPal; }

public function charge(CreditCard $creditCard, Money $forAmount) { $token = $this->payPal->createMethodToken($creditCard); $this->payPal->createTransaction($token, $forAmount); }}

Page 116: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

class FakePaymentProvider implements PaymentProvider{ // ...}

Page 117: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)
Page 118: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Design is all about dependencies.

Page 119: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Think about the design!

Page 120: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Learn your code.

Page 121: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Listen to your tests.

Page 122: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

”Always leave the code a little better than you found it.”

Page 123: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

But...

Page 124: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Be pragmatic

Page 125: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

"By not considering the future of your code, you make your code

much more likely to be adaptable in the future."

Page 126: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

At the end of the day what matters most is a business value.

Page 127: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Know the rules well, so you can break them effectively.

Page 128: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Be pragmatic,be SOLID

Krzysztof Menżyk @kmenzyk

Thanks!

Page 129: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Worth reading

http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdfhttp://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOodhttps://gilesey.wordpress.com/2013/09/01/single-responsibility-principle/”Clean Code: A Handbook of Agile Software Craftsmanship” by Robert C. Martinhttp://martinfowler.com/bliki/DesignStaminaHypothesis.html

Page 130: Be pragmatic, be SOLID (at Boiling Frogs, Wrocław)

Photo Creditshttps://flic.kr/p/5bTy6C

http://www.bonkersworld.net/building-software/

https://flic.kr/p/jzCox

https://flic.kr/p/n37EXH

https://flic.kr/p/9mcfh9

https://flic.kr/p/7XmGXp

http://my.csdn.net/uploads/201205/13/1336911356_6234.jpg

http://bit.ly/1cMgkPA

https://flic.kr/p/qQTMa

http://bit.ly/1EhyGEc

https://flic.kr/p/5PyErP

http://fc08.deviantart.net/fs49/i/2009/173/c/7/The_Best_Life_Style_by_Alteran_X.jpg

https://flic.kr/p/4Sw9pP

https://flic.kr/p/8RjbTS

https://flic.kr/p/pMFZ9n

https://flic.kr/p/zrbMdo