Date post: | 15-Apr-2017 |
Category: |
Software |
Upload: | neil-crookes |
View: | 105 times |
Download: | 6 times |
Abstract
A chat about some of the most important principles in software development. Discover or get a refresher on these tried and tested techniques for designing better code.
What’s SOLID?
5 Principles
● Single Responsibility Principle
● Open / Closed Principle
● Liskov Substitution Principle
● Interface Segregation Principle
● Dependency Inversion Principle
SRP
OCP
LSPISP
DIPS.O.L.I.D
Why S.O.L.I.D?
● Maintainable
● Understandable
● Extendable
● Testable
● Debuggable
● Reusable
● Robust, less fragile
BETTERSOFTWARE
SRP
OCP
LSPISP
DIP
class Product{ public function getId() public function getName()
public function getPrice()
public function getStock()
public function setStock($stock)
public function getDescription()}
https://github.com/neilcrookes/SoSOLIDFu
class CreditCard{ public function getExpiryYear()
public function getExpiryMonth()
public function getLast4()
public function getToken()}
https://github.com/neilcrookes/SoSOLIDFu
class Gateway{ public function purchase(Shop $shop, CreditCard $card)}
https://github.com/neilcrookes/SoSOLIDFu
class Shop{ public function addToBasket(Product $product)
public function removeFromBasket(Product $product)
public function getTotal()
public function getBasket()
public function checkout(CreditCard $card)
protected function sendOrderConfirmationEmail(CreditCard $card)}
https://github.com/neilcrookes/SoSOLIDFu
Single Responsibility
States that
● Classes should have only one reason to change
● Therefore should have only one responsibility
class Shop{ public function addToBasket(Product $product)
public function removeFromBasket(Product $product)
public function getTotal()
public function getBasket()
public function checkout(CreditCard $card)
protected function sendOrderConfirmationEmail(CreditCard $card)}
https://github.com/neilcrookes/SoSOLIDFu
class Shop{ public function addToBasket(Product $product)
public function removeFromBasket(Product $product)
public function getTotal()
public function getBasket()
public function checkout(CreditCard $card)
protected function sendOrderConfirmationEmail(CreditCard $card)}
https://github.com/neilcrookes/SoSOLIDFu
class Basket{ public function addToBasket(Product $product)
public function removeFromBasket(Product $product)
public function getTotal()
public function getBasket()}
Class Checkout{ public function checkout(CreditCard $card)
protected function sendOrderConfirmationEmail(CreditCard $card)}
https://github.com/neilcrookes/SoSOLIDFu
Class Checkout{ public function checkout(Basket $basket, CreditCard $card) { try { $reference = $this->gateway->purchase($basket, $card);
$this->sendOrderConfirmationEmail($basket, $card);
return true; } catch (GatewayException $e) { return false; } }}
https://github.com/neilcrookes/SoSOLIDFu
Open / Closed
Objects or libraries should be open for extension, but closed for modification.
● Easy to add new features OR change behaviour
● Without modifying original
● Purists: without extending original…!?
Eh?
Open / Closed cont
Extension points
● Events● Plugin architecture● Composition● Strategy pattern● Callbacks
Open / Closed cont
Closed for modification - Clarity of intent
● Final classes● Private members● Encourage extension in expected / supported way● Encourages decoupling● Reduces risk of breaking changes
final class Checkout{ public function checkout(Basket $basket, CreditCard $card) { try { $reference = $this->gateway->purchase($basket, $card);
$this->sendOrderConfirmationEmail($basket, $card);
fire(new CheckoutEvent($basket));
return true; } catch (GatewayException $e) { return false; } }}
https://github.com/neilcrookes/SoSOLIDFu
final class Checkout{ public function sendOrderConfirmationEmail(Basket $basket, CreditCard $card) { $message = "Thank you for your order.\n";
foreach ($basket as $item) { /** @var Product $product */ $product = $item['product'];
$message .= "\n" . $product->getName() . ' x ' . $item['quantity'] . ' @ £' . $product->getPrice();
} $message .= "\n\nPayment has been taken from: xxxx xxxx xxxx " . $card->getLast4()
. ' Ex: ' . $card->getExpiryMonth() . '/' . $card->getExpiryYear();
mail('[email protected]', 'Your order at my store', $message); }}
https://github.com/neilcrookes/SoSOLIDFu
Liskov Substitution
States that you should be able to swap dependencies for a subclass class without
causing unexpected results
final class Checkout{ public function sendOrderConfirmationEmail(Basket $basket, CreditCard $card) { $message = "Thank you for your order.\n";
foreach ($basket as $item) { /** @var Product $product */ $product = $item['product'];
$message .= "\n" . $product->getBasketTitle() . ' x ' . $item['quantity'] . ' @ £' . $product->getPrice();
} $message .= "\n\nPayment has been taken from: xxxx xxxx xxxx " . $card->getLast4()
. ' Ex: ' . $card->getExpiryMonth() . '/' . $card->getExpiryYear();
mail('[email protected]', 'Your order at my store', $message); }}
https://github.com/neilcrookes/SoSOLIDFu
interface PurchasableInterface{ public function getId();
public function getPrice();
public function getStock();
public function setStock($stock);
public function getBasketTitle();}
https://github.com/neilcrookes/SoSOLIDFu
Interface Segregation
● An interface should only impose the methods that a client relies on
● Split unrelated interface methods into separate interfaces
interface PurchasableInterface{ public function getId();
public function getPrice();
public function getBasketTitle();}
interface StockableInterface{ public function getStock();
public function setStock($stock);}
https://github.com/neilcrookes/SoSOLIDFu
final class Checkout{ public function checkout(Basket $basket, CreditCard $card)}
class Gateway{ public function purchase(Basket $basket, CreditCard $card)}
https://github.com/neilcrookes/SoSOLIDFu
Dependency Inversion
● High level should not depend on low level modules, both should depend on abstractions
● Abstractions should not depend upon details. Details should depend upon abstractions.
● Depend on abstractions (abstracts / interfaces), not concretes● Code to an Interface
interface ChargeableInterface{ public function getChargeableAmount();}
interface GatewayInterface{ public function charge(ChargeableInterface $chargeable,
PaymentMethodInterface $paymentMethod);}
interface PaymentMethodInterface{ public function getToken();
public function getDetails();}
https://github.com/neilcrookes/SoSOLIDFu
final class Checkout{ public function checkout(ChargeableInterface $basket, PaymentMethodInterface $card)}
class Gateway{ public function purchase(ChargeableInterface $basket, PaymentMethodInterface $card)}
https://github.com/neilcrookes/SoSOLIDFu
Conclusion:Get Classy
● Actually only add some Interfaces, and an Event● Our code is designed much better● Easy to understand, extend● More robust, less likely to introduce bugs● Lots of classes is fine● Lots of interfaces is fine