Ce diaporama a bien été signalé.
Nous utilisons votre profil LinkedIn et vos données d’activité pour vous proposer des publicités personnalisées et pertinentes. Vous pouvez changer vos préférences de publicités à tout moment.

How I started to love design patterns

1 080 vues

Publié le

We, as developers, often think that we don’t have to or don’t need to know what are what they call design patterns. We think that we already know how to build a software and don’t need all this theory. Years after years, by having to deal with the low maintainability of my own codebases, I explored a lot of ways of decoupling applications, in order to have enterprise-grade software that last for years. With concrete examples, I want to share with you some design patterns and how they can help you to grow well structured and decoupled applications.

Publié dans : Ingénierie
  • Soyez le premier à commenter

How I started to love design patterns

  1. 1. How I started to love what they call Design Patterns @samuelroze
  2. 2. Samuel Roze Software Enginner @ Inviqa Founder @ ContinuousPipe.io 4 twitter.com/samuelroze 4 github.com/sroze 4 sroze.io
  3. 3. I started years ago...
  4. 4. I probably wrote that <?php include 'header.php'; include 'pages/'. $_GET['page'].'.php'; include 'footer.php';
  5. 5. At some point I used Symfony class EntityController extends Controller { public function myAction($identifier) { $entity = $this->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository(Entity::class) ->find($identifier); return $this->render('my-template.html.twig', [ 'entity' => $entity, ]); } }
  6. 6. Then I updated my entity... public function myAction(Request $request, $identifier) { // ... if ($request->isMethod('POST')) { $form->handleRequest($request); if ($form->isValid()) { $entity = $form->getData(); $doctrine->persist($entity); $doctrine->flush($entity); } } // ... }
  7. 7. Then I even sent an email // ... if ($form->isValid()) { $entity = $form->getData(); $doctrine->persist($entity); $doctrine->flush($entity); $mailer = $this->getContainer()->get('mailer'); $mailer->send( // ... ); } // ...
  8. 8. And then... It became unmaintanable
  9. 9. Design Patterns
  10. 10. A general reusable solution to a commonly occurring problem within a given context. 1 Wikipedia
  11. 11. All the design patterns do the same thing: control the information flow. 1 Anthony Ferrara
  12. 12. Let's do some refactoring class EntityController extends Controller { public function myAction(Request $request, $identifier) { $entity = $this->getContainer() ->get('doctrine.orm.entity_manager') ->getRepository(Entity::class) ->find($identifier); $form = $this->createForm(EntityFormType::class, $entity); $form->handleRequest($request); if ($form->isValid()) { $entity = $form->getData(); $doctrine->persist($entity); $doctrine->flush($entity); $mailer = $this->getContainer()->get('mailer'); $mailer->send( Swift_Message::newInstance() ->setBody('Hey, something was updated!') ); } return $this->render('my-template.html.twig', [ 'entity' => $entity, ]); } }
  13. 13. Why do we refactor? 4 We want to reuse code
  14. 14. Why do we refactor? 4 We want to reuse code 4 So we delegate the responsibilities
  15. 15. Why do we refactor? 4 We want to reuse code 4 So we delegate the responsibilities 4 And improve the readability
  16. 16. Why do we refactor? 4 We want to reuse code 4 So we delegate the responsibilities 4 And improve the readability 4 And therefore we ensure maintainability
  17. 17. Why do we refactor? 4 We want to reuse code 4 So we delegate the responsibilities 4 And improve the readability 4 And therefore we ensure maintainability 4 By doing so we enable change
  18. 18. Adapter
  19. 19. Adapter interface EntityRepository { public function find($identifier) : Entity; public function save(Entity $entity) : Entity; }
  20. 20. Adapter final class DoctrineEntityRepository implements EntityRepository { private $entityManager; public function __construct(EntityManager $entityManager) { $this->entityManager = $entityManager; } public function find($identifier) : Entity { if (null === ($entity = $this->getDoctrineRepository()->find($identifier))) { throw new EntityNotFound(); } return $entity; } // ... }
  21. 21. Adapter final class DoctrineEntityRepository implements EntityRepository { // ... public function save(Entity $entity) : Entity { $entity = $this->entityManager->merge($entity); $this->entityManager->persist($entity); $this->entityManager->flush($entity); return $entity; } }
  22. 22. class EntityController extends Controller { public function myAction(Request $request, $identifier) { $entity = $this->getEntityRepository()->find($identifier); // ... if ($form->isValid()) { $this->getEntityRepository()->save($entity); $mailer = $this->getContainer()->get('mailer'); $mailer->send( Swift_Message::newInstance() ->setBody('Hey, something was updated!') ); } } }
  23. 23. /** @Route(service="controller.entity") **/ class EntityController { public function __construct(EntityRepository $entityRepository, /** ... **/) { /** ... **/ } public function myAction(Request $request, $identifier) { $entity = $this->entityRepository->find($identifier); // ... if ($form->isValid()) { $this->entityRepository->save($entity); $this->mailer->send( Swift_Message::newInstance() ->setBody('Hey, something was updated!') ); } } }
  24. 24. The XML bit <service id="entity.repository.doctrine" class="AppInfrastructureDoctrineEntityRepository"> <argument type="service" id="doctrine.orm.entity_manager" /> </service>
  25. 25. The XML bit <service id="entity.repository.doctrine" class="AppInfrastructureDoctrineEntityRepository"> <argument type="service" id="doctrine.orm.entity_manager" /> </service> <service id="entity.repository" alias="entity.repository.doctrine" />
  26. 26. The XML bit <service id="entity.repository.doctrine" class="AppInfrastructureDoctrineEntityRepository"> <argument type="service" id="doctrine.orm.entity_manager" /> </service> <service id="entity.repository" alias="entity.repository.doctrine" /> <service id="controller.entity" class="AppBundleControllerEntityController"> <argument type="service" id="entity.repository" /> <argument type="service" id="form.factory" /> <argument type="service" id="mailer" /> </service>
  27. 27. Store the entity in-memory? final class InMemoryRepositoryEntity implements EntityRepository { private $entities = []; public function find($identifier) : Entity { if (!array_key_exists($identifier, $this->entities)) { throw new EntityNotFound(); } return $this->entities[$identifier]; } public function save(Entity $entity) : Entity { $this->entities[$entity->getIdentifier()] = $entity; return $entity; } }
  28. 28. Event Dispatcher
  29. 29. Dispatching the event class MyController { public function myAction(Request $request, $identifier) { // ... if ($form->isValid()) { $this->entityRepository->save($entity); $this->eventDispatcher->dispatch( EntitySaved::NAME, new EntitySaved($entity) ); } // ... } }
  30. 30. An event class EntitySaved extends Event { const NAME = 'entity.saved'; private $entity; public function __construct(Entity $entity) { $this->entity = $entity; } public function getEntity() : Entity { return $this->entity; } }
  31. 31. A listener final class SendAnEmailWhenEntityIsSaved { public function __construct(MailerInterface $mailer) { /** ... **/ } public function onEntitySaved(EntitySaved $event) { $this->mailer->send(/** something **/); } }
  32. 32. Because we want some XML <service id="controller.entity" class="AppBundleControllerEntityController"> <argument type="service" id="entity.repository" /> <argument type="service" id="form.factory" /> <argument type="service" id="event_dispatcher" /> </service> -
  33. 33. Because we want some XML <service id="controller.entity" class="AppBundleControllerEntityController"> <argument type="service" id="entity.repository" /> <argument type="service" id="form.factory" /> <argument type="service" id="event_dispatcher" /> </service> <service id="entity.listener.send_mail_when_saved" class="AppEntityListenerSendAnEmailWhenEntityIsSaved"> <argument type="service" id="mailer" /> <tag name="kernel.event_listener" event="entity.saved" /> </service>
  34. 34. What is the point? 4 We can create another listener easily 4 We just have to dispatch this event
  35. 35. Decorator
  36. 36. final class DispatchAnEventWhenEntityIsSaved implements EntityRepository { public function __construct( EntityRepository $decoratedRepository, EventDispatcherInterface $eventDispatcher ) { /** ... **/ } public function find($identifier) : Entity { return $this->decoratedRepository($identifier); } public function save(Entity $entity) : Entity { $saved = $this->decoratedRepository->save($entity); $this->eventDispatcher->dispatch( EntitySaved::NAME, new EntitySaved($saved) ); return $saved; } }
  37. 37. Decorates the repository <service id="entity.repository.dispatch_an_event_when_saved" class="AppEntityRepositoryDispatchAnEventWhenEntityIsSaved" decorates="entity.repository"> <argument type="service" id="entity.repository.dispatch_an_event_when_saved.inner" /> <argument type="service" id="event_dispatcher" /> </service>
  38. 38. We just have to save class MyController { public function myAction(Request $request, $identifier) { // ... if ($form->isValid()) { $this->entityRepository->save($entity); } // ... } }
  39. 39. Decorates all the things!
  40. 40. What do we have? 4 Flexible entity storage 4 Events dispatched regarding the storage 4 Optional actions on events 4 Easily testable code!
  41. 41. Good practices, design patterns, they just help us to enable change
  42. 42. How to apply that... 4 Closes the door! 4 Uses final keyword 4 private properties 4 Create extension points when required 4 Prevent in case of
  43. 43. Maintainability 4 Distinct features in their own namespaces 4 Interfaces's names should reflect their responsibilities 4 Implementations' names should reflect their distinction
  44. 44. Thank you! @samuelroze continuouspipe.io https://joind.in/talk/187a4

×