Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

Beyond Design Principles and Patterns

1.279 Aufrufe

Veröffentlicht am

Talk at AFUP Forum PHP 2018 in Paris.

Veröffentlicht in: Software
  • Loggen Sie sich ein, um Kommentare anzuzeigen.

Beyond Design Principles and Patterns

  1. 1. Beyond Design Principles & Patterns Writing good OO code Matthias Noback @matthiasnoback
  2. 2. Design patterns ● Abstract factory ● Mediator ● Proxy ● Builder ● Composite ● Chain of responsibility ● Adapter ● Strategy ● Façade ● Bridge ● Observer ● Singleton ● Factory method ● Command
  3. 3. Design principles, and more patterns ● Class principles (SOLID) ● Package design principles ● Tactical DDD patterns ● Testing patterns ● Architectural patterns
  4. 4. Education ● Refer to books (but nobody reads them) ● In interviews: do you know SOLID? ● Knowing is one thing, applying is something else.
  5. 5. Back to basics
  6. 6. Any class can serve as the blueprint of an object, but not every object will be a Good Objecttm
  7. 7. Objects introduce meaning By wrapping primitive values Class name = Type
  8. 8. Strings Email addresses
  9. 9. Floats Latitude
  10. 10. If not every string counts as an email address, introduce a dedicated type: EmailAddress. If not every integer counts as an age,introducea dedicated type: Age. And so on!
  11. 11. Objectskeeptogetherwhatbelongs together Latitude and Longitude X and Y Amount and Currency Distance and Unit ...
  12. 12. Cohesion What is together belongs together.
  13. 13. Cohesion What belongs together, gets together. Objects attract related data and behavior.
  14. 14. Objectsbringtogethermeaningful behaviors Coordinates.distanceTo(Coordinates other): Distance Money.convertTo(Currency other): Money Map.set(String key, T item): void Map.get(String key): T Array.push(T item): void Array.pop(): T
  15. 15. Objects State & Behavior
  16. 16. final class Coordinates { private Latitude $latitude; private Longitude $longitude; public function halfwayTo( Coordinates $other): Coordinates { // ... } }
  17. 17. State Behavior Primitive values Entities Value objects Services Anemic domain objects
  18. 18. For all objects Make sure they can't exist in an incoherent or inconsistent state
  19. 19. At construction time ● Provide the right data (values, value objects) ● Provide the right services (collaborating objects)
  20. 20. // no setter injection $service = new Service(); $service->setLogger(...); // no setter party $object = new Object(); $object.setFoo(...); $object.setBar(...);
  21. 21. At modification time ● Provide the right data (values, value objects)
  22. 22. What's the “right” data? ● Valid (correct types, correct number of things, etc.) ● Meaningful (within an allowed range, pattern, etc.)
  23. 23. What's the “right” data? Only allow transitions that make sense final class Order { public function cancel(...): void { if (wasShipped) { throw new LogicException(...); } } }
  24. 24. Whenimplementingbehavior:follow this recipe public function someMethod(int $value) { // check pre-conditions Assertion::greaterThan($value, 0); // fail early if (...) { throw new RuntimeException(...); } // happy path: at "0" indent // check post-conditions return ...; }
  25. 25. Command/Query Separation Every method is either a command or a query method.
  26. 26. public function commandMethod(...): void { /* * Changes observable state of the system * * May have side effects: * - network calls * - filesystem changes * * Returns nothing. * May throw an exception. */ }
  27. 27. public function queryMethod(...): [specific type] { /* * Returns something, doesn't change * anything. * * May throw an exception. */ }
  28. 28. CQS principle Asking for information doesn’t change observable state.
  29. 29. Return single-type values ● Object of specific type, or an exception ● List with values of specific type, or an empty list
  30. 30. public function queryMethod(...): [specific type] { if (...) { throw new RuntimeException(...); } return objectOfSpecificType; }
  31. 31. public function queryMethod(...): List { if (...) { return List::empty(); } return List::of(...); }
  32. 32. At failure time Throw useful and detailed exceptions
  33. 33. Defensive programming Recover from failure? No, usually: scream about it!
  34. 34. Offensive programming Add lots of sanity checks and throw exceptions. Assert::greaterThan(...); Assert::count(...); ...
  35. 35. Offensive programming Run static analysis tools, like PHPStan. DateTime::createFromFormat() returns bool|DateTime
  36. 36. Offensive programming Introduce strictly typed alternatives to PHP's weakly typed functions. public static function createFromFormat( string $format, string $date ): DateTimeImmutable { $result = DateTimeImmutable::createFromFormat( $format, $date ); if ($result === false) { throw new RuntimeException(...); } return $result; }
  37. 37. State versus behavior, revisited Objects have state and behavior But they hide data and implementation details
  38. 38. State versus behavior, revisited Expose more behavior, less state
  39. 39. Treat your objects as black boxes When writing tests for them
  40. 40. Don'ttestconstructorsbycalling getters Give the object reason to remember something, and describe that reason in a test.
  41. 41. public function it_can_be_constructed(): void { $money = new Money(1000, new Currency('EUR')); assertEquals( 1000, $money->getAmount() ); assertEquals( 'EUR', $money->getCurrency()->asString() ); } Getters just for testing!
  42. 42. public function it_can_be_converted(): void { $money = new Money(1000, new Currency('EUR')); $rate = new ExchangeRate( new Currency('EUR'), new Currency('USD'), 1.23456 ); $converted = $money->convert($rate); assertEquals( new Money( 1235, // rounded to the second digit new Currency('USD') ) $converted ); } No getters! Nor testing them!
  43. 43. Guiding experiment Only add a getter if something other than a test needs it.
  44. 44. Changing the behavior of an object Always aim to do it without touching the code of its class (let it remain a black box!)
  45. 45. class Game { public function move(): void { $steps = $this->roll(); } private function roll(): int { return mt_rand(1, 6); } } How to use a hard-coded value for testing?
  46. 46. class Game { public function move(): void { $steps = $this->roll(); } protected function roll(): int { return mt_rand(1, 6); } } class GameWithFixedRoll extends Game { protected function roll(): int { return 6; } }
  47. 47. final class Game { private Die $die; public function __construct(Die $die) { $this->die = $die; } public function move(): void { $steps = $this->die->roll(); } } interface Die { public function roll(): int; } Dependency injection! Composition instead of inheritance Final classes!
  48. 48. final RandomDie implements Die { public function roll(): int { return mt_rand(1, 6); } } final FixedDie implements Die { public function __construct($value) { $this->value = $value; } public function roll(): int { return $this->value; } } Technically a test double called “stub” Behavior can be modified without touching the code
  49. 49. “Composition over inheritance” Changing the behavior of objects by composing them in different ways (as opposed to using inheritancetooverride behavior)
  50. 50. Create better objects ● Introduce more types. ● Be more strict about them. ● Design objects that only accept valid data. ● Design objects that can only be used in valid ways. ● Use composition instead of inheritance to change an object's behavior.
  51. 51. Well designed objects lead to an application that almost clicks together
  52. 52. Yeah, like Lego...
  53. 53. https://joind.in/talk/c2deb Matthias Noback @matthiasnoback

×