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

Applied OOP Design Patterns: Rolling with the P...

Applied OOP Design Patterns: Rolling with the Punches

Applying established design patterns in your object-oriented codebase can help you to "roll with the punches" of evolving business requirements. In this presentation, we'll take a practical look at design principles and patterns as we iteratively build a package with increasingly challenging feature requests. The key acronym in our tool belt? SOLID. (NOTE: This presentation will use an OOP-style pseudo-code language to appeal to developers of more than just one language.)

Jeremy Lindblom

October 12, 2019
Tweet

More Decks by Jeremy Lindblom

Other Decks in Programming

Transcript

  1. Applied Design Patterns: Rolling with the Punches O S L

    I D O P R Y S I K N G A Y S I R P D I
  2. Keep it Simple? ▪ To Understand? ▪ To Write? ▪

    To Instantiate? 7 ▪ To Maintain? ▪ To Test? ▪ To Change?
  3. Keep it Simple? ▪ To Understand? ▪ To Write? ▪

    To Instantiate? 8 ▪ To Maintain? ▪ To Test? ▪ To Change?
  4. SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪

    Interface Segregation ▪ Dependency Inversion 12 }Principle
  5. SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪

    Interface Segregation ▪ Dependency Inversion 13 Does one thing
  6. SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪

    Interface Segregation ▪ Dependency Inversion 14 Extend without modification
  7. SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪

    Interface Segregation ▪ Dependency Inversion 15 Child can replace a parent
  8. SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪

    Interface Segregation ▪ Dependency Inversion 16 Small interfaces
  9. SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪

    Interface Segregation ▪ Dependency Inversion 17 Rely on abstractions
  10. 19

  11. 20

  12. 21

  13. 22

  14. SOLID ▪ Single Responsibility ▪ Open-Closed ▪ Liskov Substitution ▪

    Interface Segregation ▪ Dependency Inversion 24 }Principle
  15. Applied Design Patterns: Rolling with the Punches O S L

    I D O P R Y S I K N G A Y S I R P D I
  16. Applied Design Patterns: Rolling with the Punches O S L

    I D O P R Y S I K N G A Y S I R P D I
  17. 27

  18. 28

  19. 29

  20. 32 // The Simple Approach class KinesisStream { func new(KinesisClient

    client, string streamName); func write(Message message): void; } class Message { func new(map data); func getData(): map; func toJson(): string; } // Example stream = new KinesisStream(client, "app-events"); stream.write(new Message(["foo": "bar"])); YAGNI SRP
  21. 34 // Adding New Functionality? class Stream { func new(KinesisClient

    kinesisClient, SqsClient sqsClient, string streamName, string streamType ); func write(Message message): void { if (this.streamType === "kinesis") this.kinesisClient.(…) elif (this.streamType === "sqs") this.sqsClient.(…) else throw new Exception("…"); } } SRP OCP
  22. 35 // Adding New Functionality? class Stream { func new(KinesisClient

    kinesisClient, SqsClient sqsClient, string streamName, string streamType ); func write(Message message): void { if (this.streamType === "kinesis") this.kinesisClient.(…) elif (this.streamType === "sqs") this.sqsClient.(…) else throw new Exception("…"); } } SRP OCP
  23. 36 // Interfaces And Multiple Implementations interface Stream { func

    write(Message message): void; } class KinesisStream implements Stream { func new(KinesisClient client, string streamName); func write(Message message): void; } class SqsStream implements Stream { func new(SqsClient client, string queueUrl); func write(Message message): void; } SRP ISP
  24. 37 // Factories Are Your Friends class EventsFactory { func

    createEventStream(): Stream { switch (this.config.get("events.stream.type")) { case "kinesis": return this.createKinesisStream(); case "sqs": return this.createSqsStream(); // ... } func createKinesisStream(): KinesisStream; func createSqsStream(): SqsStream; } SRP LSP
  25. 38 // Polymorphism Can Be Easy class OrdersController { func

    new(Stream stream, OrdersRepository orders) {/* ... */} // ... func doSomethingWithAnOrder(string orderId) { // ... order = this.orders.get(orderId); this.stream.write(new Message([ "order_id": order.id, "item_count": order.items.count(), ])); // ... } } DIP
  26. 40 // Decorators Are Cool class LoggingStream implements Stream {

    func new(Stream stream, LoggerInterface logger); func write(Message message): void { this.stream.write(message); this.logger.info('Message sent', message.getData()); }; } // Example stream = new LoggingStream(kinesisStream, logger); stream.write(message); SRP OCP LSP DIP
  27. 41 We are moving to SQS, but some systems haven’t

    yet. Can you send to both for now?
  28. 42 // Aggregates Can Work Too class MultiStream implements Stream

    { func new(Stream ...streams); func write(Message message): void; } // Example stream = new LoggingStream( new MultiStream(kinesisStream, sqsStream), logger ); stream.write(message); SRP OCP LSP DIP
  29. 43

  30. 45 // Messages Get a Structure class Message extends JsonSerializable

    { func new(Header header, array data); func getHeader(): Header; func getData(): map; func toJson(): string; } class Header extends JsonSerializable { func new(string id, …, DateTime timestamp); func getId(): string; func getSource(): string; func getTopic(): string; func getAction(): string; func getTimestamp(): DateTime; func toJson(): string; }
  31. 46 // A New Layer of Abstraction class EventPublisher {

    func new(Stream stream, string source); func publish(Event event): void; } interface Event { func getTopic(): string; func getAction(): string; func getData(): map; } publisher.publish(event); SRP DIP
  32. 47 // A New Layer of Abstraction class OrderEvent implements

    Event { func new(Order order, string action); // ... } class OrderCreatedEvent extends OrderEvent { func new(Order order); // ... } publisher.publish(new OrderCreatedEvent(order)); OCP LSP
  33. 49 // Polymorphism Instead of Branching class EventPublisher { func

    new(Stream stream, string source, bool on = true) { // … } func publish(Event event): void { if (this.on) { // Do stuff. } }; } OCP
  34. 50 // Polymorphism Instead of Branching interface Publisher { func

    publish(Event event): void; } class EventPublisher { func new(Stream stream, string source); func publish(Event event): void; } class NullPublisher { func publish(Event event): void { // Do nothing. }; } LSP DIP
  35. 51 // Conditionalize Your Factory Instead of Code class EventsFactory

    { // ... func createEventPublisher(): Publisher { if (!this.config.get("events.enabled")) { return new NullPublisher(); } return new EventPublisher( this.createEventStream(), this.config.get("events.source") ); } } SRP LSP
  36. 53 // Under the Hood Changes interface TopicMapper { func

    mapTopic(string topic): string; } class KinesisStream implements Stream { func new(KinesisClient client, TopicMapper tm); func write(Message message): void; } class SqsStream implements Stream { func new(SqsClient client, TopicMapper tm); func write(Message message): void; } SRP LSP ISP
  37. 55 // New Streams? No Biggie! class HttpStream implements Stream

    { func new(HttpClient client, string url); func write(Message message): void; } class CustomServiceStream implements Stream { func new(CustomServiceClient client); func write(Message message): void; }
  38. 56 We want to send lots of messages at a

    time. Can we send them in batches?
  39. 58 // Is There a Good Way? class CustomServiceBatchStream extends

    CustomServiceStream { func write(Message message): void func flush(): void; } OCP
  40. 59 // Is There a Good Way? interface Stream {

    func write(Message message): void; func flush(): void; } class CustomServiceBatchStream implements Stream { func write(Message message): void func flush(): void; } ISP
  41. 60 // Multiple Interfaces & Additive Changes interface Stream {

    func write(Message message): void; } interface BatchStream extends Stream { func flush(): void; } class CustomServiceBatchStream implements BatchStream { func new(CustomServiceClient client); func write(Message message): void; func flush(): void; } ISP
  42. Final Thoughts ▪ Software is complicated, because life is complicated.

    ▪ You can’t predict the future, but you can prepare for it. ▪ Learn from others’ experiences, but experience it for yourself. 61 O S L I D I P P P P P R C S S
  43. 62 class MultiStream implements Stream { func new(Stream ...streams); func

    write(Message message): void { }; } // Example stream = new LoggingStream( new MultiStream(kinesisStream, sqsStream), logger ); stream.write(message); Can anyone guess the programming language?
  44. 63 class MultiStream implements Stream { public function __construct(Stream ...$streams)

    { }; public function write(Message $message): void { }; } // Example $stream = new LoggingStream( new MultiStream($kinesisStream, $sqsStream), $logger ); $stream->write($message); It’s PHP!