Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SOLID - It's Not Just a State of Matter, It's Principles for OO Propriety

SOLID - It's Not Just a State of Matter, It's Principles for OO Propriety

Principles abound our lives. Software and engineering is no exception. The more you develop software, the more patterns and principles you master. Yet, solutions remain difficult to maintain and developers are frequently frustrated by impediments they introduce and the waste generated to get around them. But why do we run into these roadblocks? It could be that you're not using the principles of object-oriented development to build better solutions. The SOLID principles are an important foundation for how to leverage OO languages to produce high value, low overhead, maintainable solutions. Whether you've just starting to sling code or have been a veteran in the industry, the principles taught in this presentation are invaluable.

60027628f00ca746d63cc27465450c56?s=128

neraath

July 01, 2012
Tweet

Transcript

  1. SOLID Not Just a State of Matter, It’s Principles for

    OO Propriety Chris Weldon Saturday, June 30, 12
  2. Me • Fightin’ Texas Aggie • PHP Developer since 2003

    • Senior Consultant at Improving Enterprises • mojolive.com/profile/neraath • Contact Me: chris@chrisweldon.net Saturday, June 30, 12
  3. apihackday.com Saturday, June 30, 12

  4. TOMORROW • Free! • Food, Drinks, and Beer Provided •

    $750 Amazon Cert, $250 Apple Cert, Other Prizes • Register NOW: http://apihackday.com Saturday, June 30, 12
  5. What is OOD? Saturday, June 30, 12

  6. What is OOD? Abstraction Saturday, June 30, 12

  7. What is OOD? Inheritance Abstraction Saturday, June 30, 12

  8. What is OOD? Encapsulation Inheritance Abstraction Saturday, June 30, 12

  9. What is OOD? Encapsulation Polymorphism Inheritance Abstraction Saturday, June 30,

    12
  10. ? Saturday, June 30, 12

  11. Saturday, June 30, 12

  12. An Order Saturday, June 30, 12

  13. <?php class Order { private $_id; private $_userId; private $_products;

    public function getId() { return $this->_id; } public function setId($id) { $this->_id = $id; } public function getUserId() { return $this->_userId; } public function setUserId($userId) { $this->_userId = $userId; } Saturday, June 30, 12
  14. public function getProducts() { return $this->_products; } public function addProduct($product)

    { $this->_products->add($product); } public function setProducts(Product[] $products) { $this->_products = $products; } Saturday, June 30, 12
  15. New Requirement! Calculate Cost of Order Saturday, June 30, 12

  16. <?php class OrderUtilities { public static function calculateCostOfOrder($order) { $products

    = $order->getProducts(); $cost = 0.0; foreach ($products as $product) { $cost += $product->getCost(); } return $cost; } Saturday, June 30, 12
  17. New Requirements! Fetch Orders from Database Fetch User for Order

    Saturday, June 30, 12
  18. public static function getOrderById($orderId) { if ($orderId == null ||

    $orderId < 0) { $command = $this->_db->exec("SELECT * FROM orders"); $results = $this->_db->getResults($command); // Get the first result. return $results[0]; } $command = $this->_db->exec("SELECT * FROM orders WHERE id = ?", $orderId); $order = $this->_db->getSingle($command); return $order; } Saturday, June 30, 12
  19. public static function getUserForOrder($order) { $command = $this->_db->exec("SELECT * FROM

    users WHERE id = ?", $order->getUserId()); $user = $this->_db->getSingle($command); return $user; } Saturday, June 30, 12
  20. New Requirements! Deliver Order to Customer Notify Customer of Delivery

    Saturday, June 30, 12
  21. public static function deliverOrder() { try { $networkConnection = new

    WebServiceConnection("http://shpt1/shipctrl.svc"); $networkConnection->open(); $shippingManager = new ShippingManager($networkConnection); $shippingManager->setOrder($this); $shippingManager->setDeliveryType(DeliveryType::EXPRESS); $shippingManager->shipViaWebService(); $this->notifyCustomer(); } catch (\Exception $e) { $logFile = fopen("/tmp/logfile.log", "a"); fwrite("An error occurred while delivering the order.", $logFile); fclose($logFile); } } Saturday, June 30, 12
  22. public static function notifyCustomer() { try { $mailer = new

    \Zend_Mail(); $mailer->setFrom('orders@chrisweldon.net', 'Grumpy Baby Orders'); $mailer->setSubject('Order #' . $this->getId() . ' out for Delivery'); $mailer->setBodyText('Your order is being shipped!'); $mailer->send(); } catch (\Exception $e) { self::logNotificationError($errorMessage); } } Saturday, June 30, 12
  23. public static function logDeliveryError($errorMessage) { $logFile = fopen("/tmp/logfile.log", "a"); fwrite("An

    error occurred while delivering the order.", $logFile); fwrite($errorMessage, $logFile); fclose($logFile); } public static function logNotificationError($errorMessage) { $logFile = fopen("/tmp/logfile.log", "a"); fwrite("An error occurred while emailing the notice.", $logFile); fwrite($errorMessage, $logFile); fclose($logFile); } } Saturday, June 30, 12
  24. Saturday, June 30, 12

  25. OMG Saturday, June 30, 12

  26. OMG Who vomited in my codebase? Saturday, June 30, 12

  27. Saturday, June 30, 12

  28. “There should never be more than one reason for a

    class to change” Saturday, June 30, 12
  29. Improve Saturday, June 30, 12

  30. Improve class Order { // I’m just a model }

    Saturday, June 30, 12
  31. Improve class Order { // I’m just a model }

    class OrderDao { // I just talk to the database public function CreateOrder($userId, array $products); public function getOrderById($orderId); } Saturday, June 30, 12
  32. Improve class Order { // I’m just a model }

    class OrderDao { // I just talk to the database public function CreateOrder($userId, array $products); public function getOrderById($orderId); } class OrderDeliverer { // I just deliver orders public function deliverOrder(); } Saturday, June 30, 12
  33. Improve class Order { // I’m just a model }

    class OrderDao { // I just talk to the database public function CreateOrder($userId, array $products); public function getOrderById($orderId); } class OrderDeliverer { // I just deliver orders public function deliverOrder(); } class CustomerNotifier { // I just deliver orders public function notifyCustomer(); } Saturday, June 30, 12
  34. Improve class Order { // I’m just a model }

    class OrderDao { // I just talk to the database public function CreateOrder($userId, array $products); public function getOrderById($orderId); } class OrderDeliverer { // I just deliver orders public function deliverOrder(); } class CustomerNotifier { // I just deliver orders public function notifyCustomer(); } it’s what we do Saturday, June 30, 12
  35. Zend_Log kLogger log4php log5php pear/Log Saturday, June 30, 12

  36. Next Sample Saturday, June 30, 12

  37. public function ActivateDrillBit($customerOption) { if ($customerOption == "small") { $drillBit

    = new SmallBit(); $drillBit->activate(); } else if ($customerOption == "medium") { $drillBit = new MediumBit(); $drillBit->activate("120hz"); } else if ($customerOption == "large") { $drillBit = new LargeBit(); $drillBit->activate("240hz", Options::Water); } } Saturday, June 30, 12
  38. Customer Needs to Specify Options Requirements Change! Saturday, June 30,

    12
  39. public function ActivateDrillBit($customerOption, $freq, Options $options) { if ($freq ==

    "") $freq = "240hz"; if ($options == null) $options = Options::NoWater; if ($customerOption == "small") { $drillBit = new SmallBit(); $drillBit.activate($freq, $options); } else if (customerOption == "medium") { $drillBit = new MediumBit(); $drillBit.activate($freq, $options); } else if (customerOption == "large") { $drillBit = new LargeBit(); $drillBit.activate($freq, $options); } } Saturday, June 30, 12
  40. Saturday, June 30, 12

  41. You Broke My App !@#!@ Saturday, June 30, 12

  42. Saturday, June 30, 12

  43. class DrillBitActivator { public function ActivateDrillBit($customerOption) { // ... }

    } class DrillBitConfigurableActivator implements DrillBitActivator { public __construct($freqency, Options $options) { // Configurable! } public function ActivateDrillBit($customerOption) { // ... } } Saturday, June 30, 12
  44. public function ActivateDrillBit($customerOption) /** @var IDrillBit $drillBit */ $drillBit =

    DrillBitFactory::CreateDrillBit($customerOption); $drillBit.activate($this->_freq, $this->_options); } Saturday, June 30, 12
  45. Next Saturday, June 30, 12

  46. Saturday, June 30, 12

  47. Saturday, June 30, 12

  48. A Familiar Example Saturday, June 30, 12

  49. interface IManageCustomers { function TakeSpecifications(Specs $specs); function ReviewSpecifications(Specs $specs); function

    GiveToEngineers(Specs $specs); function DealWithTheGoshDarnCustomers(); function IsPeoplePerson(); } Saturday, June 30, 12
  50. class GoodManager implements IManageCustomers { public function TakeSpecifications(Specs $specs) {

    $this->ReviewSpecifications($specs); } public function ReviewSpecifications(Specs $specs) { // If specs seem good. $this->GiveToEngineers($specs); } public function GiveToEngineers(Specs $specs) { // E-mails specs to engineers. } public function DealWithTheGoshDarnCustomers() { // You better believe he does. } public function IsPeoplePerson() { return true; // Absolutely! } } Saturday, June 30, 12
  51. Saturday, June 30, 12

  52. class Tom implements IManageCustomers { public function TakeSpecifications(Specs $specs) {

    throw new DelegateToSecretary(); } public function ReviewSpecifications(Specs $specs) { throw new DelegateToSecretary(); } public function GiveToEngineers(Specs $specs) { throw new DelegateToSecretary(); } public function DealWithTheGoshDarnCustomers() { // Your gosh darn right I do! } public function IsPeoplePerson() { return true; // I AM a people person, dammit! } } Saturday, June 30, 12
  53. Our consultants have a look at Tom Saturday, June 30,

    12
  54. Saturday, June 30, 12

  55. interface ITakeSpecifications { function TakeSpecifications(Specs $specs); } interface IReviewSpecifications {

    function ReviewSpecifications(Specs $specs); } interface IGiveToEngineers { function GiveToEngineers(Specs $specs); } interface IManageCustomers { function DealWithTheGoshDarnCustomers(); function IsPeoplePerson(); } Saturday, June 30, 12
  56. class GoodManager implements IManageCustomers, ITakeSpecifications, IReviewSpecifications, IGiveToEngineers { public function

    TakeSpecifications(Specs $specs) { $this->ReviewSpecifications($specs); } public function ReviewSpecifications(Specs $specs) { // If specs seem good. $this->GiveToEngineers($specs); } public function GiveToEngineers(Specs $specs) { // E-mails specs to engineers. } public function DealWithTheGoshDarnCustomers() { // You better believe he does. } public function IsPeoplePerson() { return true; // Absolutely! } } Saturday, June 30, 12
  57. class Tom implements IManageCustomers { public function DealWithTheGoshDarnCustomers() { //

    Your gosh darn right I do! } public function IsPeoplePerson() { return true; // I AM a people person, dammit! } } Saturday, June 30, 12
  58. Next Up Saturday, June 30, 12

  59. Saturday, June 30, 12

  60. interface IDataResource { function Load(); function Save(); } Saturday, June

    30, 12
  61. class AppSettings implements IDataResource { public function Load() { //

    Load application settings. } public function Save() { // Save application settings. } } Saturday, June 30, 12
  62. class UserSettings implements IDataResource { public function Load() { //

    Load user settings. } public function Save() { // Save user settings. } } Saturday, June 30, 12
  63. static function LoadAll() { $resources = array( new AppSettings(), new

    UserSettings() ); foreach ($resources as $resource) { $resource->Load(); } return $resources; } static function SaveAll(array $resources) { foreach ($resources as $resource) { $resource->Save(); } } Saturday, June 30, 12
  64. Our “Duck” Saturday, June 30, 12

  65. class UnsaveableSettings implements AppSettings { public function Load() { //

    Loads settings. } public function Save() { throw new CannotSaveException(); } } Saturday, June 30, 12
  66. static function LoadAll() { $resources = array( new AppSettings(), new

    UserSettings(), new UnsaveableSettings() ); foreach ($resources as $resource) { $resource->Load(); } return $resources; } static function SaveAll(array resources) { foreach ($resources as $resource) { $resource->Save(); } } Saturday, June 30, 12
  67. static function LoadAll() { $resources = array( new AppSettings(), new

    UserSettings(), new UnsaveableSettings() ); foreach ($resources as $resource) { $resource->Load(); } return $resources; } static function SaveAll(array resources) { foreach ($resources as $resource) { $resource->Save(); } } Saturday, June 30, 12
  68. static function LoadAll() { $resources = array( new AppSettings(), new

    UserSettings(), new UnsaveableSettings() ); foreach ($resources as $resource) { $resource->Load(); } return $resources; } static function SaveAll(array resources) { foreach ($resources as $resource) { $resource->Save(); } } What happens with UnsaveableSettings? Saturday, June 30, 12
  69. Saturday, June 30, 12

  70. static function SaveAll(array $resources) { foreach ($resources as $resource) {

    if ($resource instanceof UnsaveableSettings) continue; $resource->Save(); } } teh fix! Saturday, June 30, 12
  71. static function SaveAll(array $resources) { foreach ($resources as $resource) {

    if ($resource instanceof UnsaveableSettings) continue; $resource->Save(); } } teh fix! Saturday, June 30, 12
  72. static function SaveAll(array $resources) { foreach ($resources as $resource) {

    if ($resource instanceof UnsaveableSettings) continue; $resource->Save(); } } teh fix! omg shoot me good job reducing abstraction Saturday, June 30, 12
  73. The Real Fix Saturday, June 30, 12

  74. The Real Fix Interface Segregation + Polymorphism Saturday, June 30,

    12
  75. interface IDataResource { function Load(); } interface ISaveResource { function

    Save(); } Saturday, June 30, 12
  76. public class AppSettingsLoaderBase implements IDataResource { public void Load() {

    // Load application settings. } } Saturday, June 30, 12
  77. public class AppSettings extends AppSettingsLoaderBase implements ISaveResource { public function

    Save() { // Save application settings. } } Saturday, June 30, 12
  78. public class UnsaveableSettings extends AppSettingsLoaderBase { public void Load() {

    // Loads settings. } } Saturday, June 30, 12
  79. static function LoadAll() { $resources = array( new AppSettings(), new

    UserSettings(), new UnsaveableSettings() ); foreach ($resources as $resources) { $resource->Load(); } return $resources; } static function SaveAll(SaveResourcesCollection $resources) { foreach ($resources as $resource) { $resource->Save(); } } Saturday, June 30, 12
  80. Final Problem Saturday, June 30, 12

  81. class Authenticator { /** @var DataAccessLayer $_repository */ private $_repository;

    public __construct() { $this->_repository = new DataAccessLayer(); } public function authenticate($username, $password) { $hashedPassword = md5($password); $user = $this->_repository->findByUsernameAndPassword( $username, $hashedPassword); return $user == null; } } Saturday, June 30, 12
  82. Problems? Saturday, June 30, 12

  83. Problems? authenticate() : bool Authenticator findByUsernameAndPassword : array DataAccessLayer Saturday,

    June 30, 12
  84. Problems? authenticate() : bool Authenticator findByUsernameAndPassword : array DataAccessLayer Strongly

    coupled to DataAccessLayer Saturday, June 30, 12
  85. Saturday, June 30, 12

  86. Dependency Inversion • “High-level modules should not depend upon low

    level modules. They should depend upon abstractions. • “Abstractions should not depend upon details. Details should depend upon abstractions.” Robert Martin Saturday, June 30, 12
  87. Step 1 Invert Dependency Saturday, June 30, 12

  88. class Authenticator { private $_repository; public __construct(DataAccessLayer $repository) { $this->_repository

    = $repository; } public function authenticate($username, $password) { $hashedPassword = md5($password); $user = $this->_repository->findByUsernameAndPassword( $username, $hashedPassword); return $user == null; } } Saturday, June 30, 12
  89. Coupling = Bad Saturday, June 30, 12

  90. Step 2 Depend on Abstractions Saturday, June 30, 12

  91. interface IUserRepository { function findByUsernameAndPassword($username, $password); } class DataAccessLayer implements

    IUserRepository { public function findByUsernameAndPassword($username, $password) { // Do some database stuff. } } Saturday, June 30, 12
  92. class Authenticator { /** @var IUserRepository $_repository private $_repository; public

    __construct(IUserRepository $repository) { $this->_repository = $repository; } public function authenticate($username, $password) { $hashedPassword = md5($password); $user = $this->_repository->findByUsernameAndPassword( $username, $hashedPassword); return $user == null; } } Saturday, June 30, 12
  93. Comparison Saturday, June 30, 12

  94. Comparison authenticate() : bool Authenticator findByUsernameAndPassword : array DataAccessLayer Saturday,

    June 30, 12
  95. Comparison authenticate() : bool Authenticator findByUsernameAndPassword : array DataAccessLayer Saturday,

    June 30, 12
  96. Comparison authenticate() : bool Authenticator findByUsernameAndPassword : array DataAccessLayer authenticate()

    : bool Authenticator findByUsernameAndPassword : array IUserRepository findByUsernameAndPassword : array DataAccessLayer Saturday, June 30, 12
  97. Benefit: Flexibility class WebServiceUserRepository implements IUserRepository { public function findByUsernameAndPassword($username,

    $password) { // Fetch our user through JSON or SOAP } } public class OAuthRepository implements IUserRepository { public function findByUsernameAndPassword($username, $password) { // Connect to your favorite OAuth provider } } Saturday, June 30, 12
  98. Recap Saturday, June 30, 12

  99. Saturday, June 30, 12

  100. S = SRP - Single Responsibility Principle Saturday, June 30,

    12
  101. S = SRP - Single Responsibility Principle O = OCP

    - Open/Closed Principle Saturday, June 30, 12
  102. S = SRP - Single Responsibility Principle O = OCP

    - Open/Closed Principle L = LSP - Liskov Substitution Principle Saturday, June 30, 12
  103. S = SRP - Single Responsibility Principle O = OCP

    - Open/Closed Principle L = LSP - Liskov Substitution Principle I = ISP - Interface Segregation Principle Saturday, June 30, 12
  104. S = SRP - Single Responsibility Principle O = OCP

    - Open/Closed Principle L = LSP - Liskov Substitution Principle I = ISP - Interface Segregation Principle D = DIP - Dependency Inversion Principle Saturday, June 30, 12
  105. ? Saturday, June 30, 12

  106. Is it law? Saturday, June 30, 12

  107. They be more like “guidelines” Saturday, June 30, 12

  108. Questions? Saturday, June 30, 12

  109. http://joind.in/talk/view/6345 Thanks! Saturday, June 30, 12

  110. Dependency Injection Saturday, June 30, 12

  111. What is it? Saturday, June 30, 12

  112. Saturday, June 30, 12

  113. Involves a Service Container Involves a Service Container Saturday, June

    30, 12
  114. sfServiceContainer Subclass <?php class UserRepositoryContainer extends sfServiceContainer { protected function

    getUserRepositoryService() { $container = new DataAccessLayer($this['repository.config']); return $container; } } Saturday, June 30, 12
  115. Just Ask The Container for a Service Saturday, June 30,

    12
  116. Consuming the Container <?php class LoginController { public function login($username,

    $password) { $container = new UserRepositoryContainer(); $repository = $container->userRepository; $authenticator = new Authenticator($repository); if ($authenticator->authenticate($username, $password)) { // Do something to log the user in. } } } Saturday, June 30, 12
  117. Registers Chains of Dependencies Saturday, June 30, 12

  118. Code-Based Description <?php // Imagine this is a bootstrap file.

    $configuration = Zend_Registry::get('dbconfig'); $builder = new sfServiceContainerBuilder(); $builder->register('user_repository', 'DataAccessLayer') ->addArgument($configuration) // OR ->addArgument('%repository.config%') from earlier ->setShared(false); $builder->register('authenticator', 'Authenticator') ->addArgument(new sfServiceReference('user_repository')); Zend_Registry::set('di_container', $builder); Saturday, June 30, 12
  119. Config-Based Description <?xml version="1.0" ?> <container xmlns="http://symfony-project.org/2.0/container"> <parameters> <parameter key="user_repository.dsn">mysql://localhost/database</parameter>

    <parameter key="user_repository.username">username</parameter> <parameter key="user_repository.password">password</parameter> </parameters> <services> <service id="user_repository" class="DataAccessLayer" shared="false"> <argument type="collection"> <argument key="dsn">%user_repository.dsn%</argument> <argument key="username">%user_repository.username%</argument> <argument key="password">%user_repository.password%</argument> </argument> </service> <service id="authenticator" class="Authenticator"> <argument type="service" id="user_repository" /> </service> </services> </container> Saturday, June 30, 12
  120. Get the Object You Want Saturday, June 30, 12

  121. Using the Container <?php class LoginController { public function login($username,

    $password) { $container = Zend_Registry::get('di_container'); $authenticator = $container->authenticator; if ($authenticator->authenticate($username, $password)) { // Do something to log the user in. } } } Saturday, June 30, 12
  122. Deep Topic Saturday, June 30, 12