+
Design Patterns for RubyistsWho Said Dynamic Languages Can’t Have Patterns?Software Design Trilogy – Part II
Andy Maleh / Software Engineer / Groupon
+Outline
Design Patterns Pop Quiz
Practical Applications of Design Patterns in Ruby
Alternative Implementations in Ruby
Deprecated Design Patterns in Ruby
Summary
+Design Patterns Pop Quiz
Who are the Gang of Four?1. Group of communist party leaders in China
2. Rock band
3. Bunch of computer programmers
+Design Patterns Pop Quiz
What is a Design Pattern? Reusable solution to a common problem encountered
at the software design level of abstraction.
+Design Patterns Pop Quiz
What does a Design Pattern consist of? Name Problem Solution Consequences
+Practical Applications of Design Patterns in Ruby
Strategy
Problem: Need to customize behavior with multiple variations at run time, and possibly allow clients to provide their own customization as well
Solution: Encapsulate each behavior variation in a Strategy object
+Practical Applications of Design Patterns in Ruby - Strategy
Example: Problem: A customer can submit a payment in one of
multiple methods, such as credit card, cashier’s check, and paypal. The code is littered with conditionals that alter behavior per payment type, making it quite involved and difficult to add a new payment type in the future or maintain existing code
Solution: Separate each payment type into its own PaymentStrategy object and let it handle that payment’s details
+Practical Applications of Design Patterns in Ruby - Strategy
Code:
+Practical Applications of Design Patterns in Ruby
State
Problem: An abstraction transitions through multiple states and behaves differently under each
Solution: Encapsulate behavior variations in multiple State objects
+Practical Applications of Design Patterns in Ruby - State
Example: Problem: An order passes through multiple
states before it is processed: new unverified shipping processed
It can also be made inactive
Developers are tired of all the conditionals littering their codebase everywhere about the different behaviors associated with each state Solution: Encapsulate behavior associated with
each state in its own OrderState object, including the transitions
+Practical Applications of Design Patterns in Ruby - State
Code:
+Practical Applications of Design Patterns in Ruby
Composite
Problem: An object may be composed of other objects, like chapters consisting of sections in a book, yet both parent and child objects share common behavior
Solution: Define a child object superclass and a parent object superclass. The parent object superclass extends the child object superclass to share common behavior.
+Practical Applications of Design Patterns in Ruby - Composite
Example: Problem: An educational website needs to
handle behavior for storing and formatting a hierarchy of information consistently: chapters, sections, and pages
Solution: Define a Node superclass for all elements and a ParentNode superclass for all parent elements (chapters and sections)
+Practical Applications of Design Patterns in Ruby - Composite
Code:
+Practical Applications of Design Patterns in Ruby
Adapter
Problem: An existing object (object A) with a particular interface needs to interact with another object (object B) that requires a different interface
Solution: Create an Adapter object that wraps object A to adapt its interface to what is expected of object B.
+Practical Applications of Design Patterns in Ruby - Adapter
Example: Problem: Authentication library supports outside
strategies with an authenticate method. We have a CompanyLogin implementation that has a login method instead.
Solution: Create an CompanyAuthenticationAdapter that wraps CompanyLogin and provides an authenticate method instead that calls login
+Practical Applications of Design Patterns in Ruby
Proxy
Problem: there is a need to alter access to an object for aspect oriented reasons like caching, security, or performance, and ensure that this controlled access is enforced across the app
Solution: Create a Proxy object that wraps the original object and alters access to it by adding a caching, security, or performance related layer
+Practical Applications of Design Patterns in Ruby - Proxy
Example: Problem: we need to cache data retrieved from a
web service via a client object Solution: Create a Proxy object that wraps the
web service client and have it cache incoming data
+Practical Applications of Design Patterns in Ruby
Decorator
Problem: an object capabilities need to be enhanced without modifying the object
Solution: Create a Decorator object that wraps the original object and adds the extra capabilities
+Practical Applications of Design Patterns in Ruby - Decorator
Example: Problem: the view needs to render some model
attributes as is as well as render some formatted versions of the model attributes (example a client description that includes name and address)
Solution: Create a Decorator object that wraps the model providing its original attribute methods as well as extra formatted versions
+Practical Applications of Design Patterns in Ruby - Decorator
Code:
+Practical Applications of Design Patterns in Ruby - Decorator
Code:
+Practical Applications of Design Patterns in Ruby
Mediator
Problem: there is a need to decouple multiple objects from each other while orchestrating a particular part of work
Solution: introduce a Mediator object that is responsible for orchestrating the work between multiple objects
+Practical Applications of Design Patterns in Ruby - Mediator
Example: Problem: work associated with processing an
order requires orchestration of many objects, such as Order, PaymentGateway, AddressService, and has gone beyond the scope of what an Order object can handle
Solution: introduce an OrderProcessor mediator object that is responsible for processing an order
+Practical Applications of Design Patterns in Ruby
Bridge
Problem: there is a need to implement an abstraction in different ways or using different libraries
Solution: decouple abstraction from implementation by creating an implementation object separate from the abstraction object, which bridges the abstraction to the specific implementation
+Practical Applications of Design Patterns in Ruby - Bridge
Example: Problem: models are coupled to ActiveRecord as
their storage mechanism. We would like to decouple the ActiveRecord implementation to allow switching some models to MongoDB easily without affecting controllers and higher level models
Solution: decouple models from their storage implementations by introducing a ModelRepository object for each Model
+Practical Applications of Design Patterns in Ruby
Observer
Problem: there is a need to perform work outside an object’s responsibilities whenever the object state changes
Solution: make the object an Observable object that allows subscribing to state notifications, and introduce Observer objects that observe the Observable and react according to changes
+Practical Applications of Design Patterns in Ruby - Observer
Example: Problem: there is a need to send an email on
every order state change and we do not want such specific logic to be tied to the order model directly
Solution: introduce an EmailOrderObserver object that monitors Order for state transitions. Make Order an observable by having it provide a subscribe_to_state_transitions(observer) method to allow monitoring its state changes
+Practical Applications of Design Patterns in Ruby - Observer
Code:
+Practical Applications of Design Patterns in Ruby
Prototype
Problem: there is a need to build a complex object a certain way, with several variations
Solution: define a Prototype object for each of the variations, and implement a clone method that enables you to use them to instantiate objects easily
+Practical Applications of Design Patterns in Ruby - Prototype
Example: Problem: need to create different kinds of User
objects when writing tests that conform to different common roles: User, Admin, Visitor, etc…
Solution: define each Deal type as a prototype using the FactoryGirl library
+Practical Applications of Design Patterns in Ruby - Prototype
Code:
+Alternative Implementations in Ruby
Strategy: enumerate blocks per strategy name
State: enumerate blocks per state value
Decorator: method_missing, Forwardable, Delegator, etc…
Template Method / Factory Method: abstract library
Abstract Factory: abstract library
+Deprecated Design Patterns in Ruby
There are Design Patterns that have alternative implementations in Ruby that render them deprecated to an extent Command Blocks Iterator Iterator Methods (each, map, inject,
etc…)
+Summary
Design Patterns enable developers to honor the open-closed principle to keep their libraries open for extension, yet closed for modification
Design Patterns help improve maintainability when behavior gets complex by adding structure that improves separation of concerns
Design Patterns serve as a good design communication tool
Design Patterns in Ruby often have alternative implementations due to language features like blocks and duck-typing
+Contact Info
Andy Maleh / Software Engineer / Groupon
Blog: http://andymaleh.blogspot.com
Twitter: @AndyMaleh
+References
Design Patterns by the Gang of Four (ISBN-13:978-0201633610)
Head First Design Patterns (ISBN-13:978-0596007126)