Design Patterns in Ruby

  1. Building Better Programs with Patterns
    • people develop solutions for common problems in all domains
    • GOF popularized idea of prepackaged solutions and defined 23 patterns
    • Separate things that change from things that don’t
      • change is given
      • ideally changes are local and don’t require updates everywhere
    • Program to an interface not an implementation
      • polymorphism
      • decouples
      • code is more flexible for types of class it can work for
      • idea is more about programming to a more general type
    • Composition over inheritance
      • inheritance marries classes together
      • subclasses must know inner workings of super class
      • favor composition, build bottom up and use object references
      • increases encapsulation
    • delegate, delegate, delegate
      • flexible, but at cost of extra method calls and more boiler plate
    • You Ain’t Gonna Need It (YAGNI)
      • don’t add flexibility you don’t need
      • you are doing upfront work and betting you will need it
      • also assuming you will do it right before encountering need in real life
      • will always have more info later
      • be wary of over-engineering for infinite flexibility
  2. Getting Started with Ruby
    • overview of language basics
  3. Template Method
    • When only some portion of code needs flexibility
    • Build abstract base class with skeletal method
    • Concrete subclasses overrides methods that require specific implementation
  4. Strategy
    • Drawback of template method is inheritance
    • Strategy pattern is about pulling out the algorithm into a separate object
    • define family of objects (the strategies) that share an interface
    • data can be passed via args or passing an object reference
    • can pass procs instead of objects which might be better for simple strategies
    • avoid coupling strategy so theres no room for new strategies
  5. Observer
    • keep objects in a system in sync with some state
    • keep an array of observers and notify them all when something changes
    • can also pass in code blocks
    • pull: sends object and observer must extract relevant info
    • push: sends specific info
    • some problems 1) too many updates 2) if multiple fields change may be caught in inconsistent state 3) what if observer causes an error?
    • intent is about informing other objects of an intent
  6. Composite Pattern
    • pattern where the sum acts like one of the parts
    • instructions can be thought of as a tree
    • requires 1) common interface for components 2) need leaf classes that implement Component 3) at least one composite high-level class
    • need a strategy for distinguishing leaf nodes from composite nodes
    • common error is assuming one-level deep
  7. Iterator
    • access elements of an aggregate object without exposing underlying representation
    • external iterators e.g. keeping track of index of an array
    • internal iterators e.g. passing logic down into the aggregate data structure
    • external iterators allow more control and portability at the cost of more overhead
    • issues can arise if structure changes during iteration / make a copy
  8. Commands
    • keep track of actions
    • undo / redo: state tracking, execute, unexecute
    • sometimes easier to just do what you want
    • overhead with remembering how to do something
    • need to think through circumstances of when the command was initialized vs executed
    • good for keeping track of what you need to do or have already done
  9. Adapter
    • helps handle interfaces that don’t match
    • in ruby can modify the class
    • modification might be ok if 1) they are simple and 2) you understand the class and the way it is used
    • use adapter if 1) the interface mismatch is extensive or 2) you don’t know how the class works
    • duck-typing allows for the adapter to implement only the methods that are used, but issues if client calls methods not there
  10. Proxy
    • involves controlling access to an object
    • proxies have a hidden reference to the object
    • allows for better separation of concerns / don’t want unrelated code tangled together
    • Protection: verification or authorization
    • Remote: hide complexity of network communication
    • Virtual: can delay creating expensive objects / can pass in creation block
    • method_missing can be adapted, but need to keep in mind that all objects come with some methods
  11. Decorator
    • adds enhancement to an object
    • implements the same interface
    • might require a lot of boilerplate
    • forwardable module can simplify delegation
    • in ruby can 1) wrap methods with alias or 2) extend module
  12. Singleton
    • class with only one instance with global access
    • can use class or module
    • can tightly couple classes
    • meant to model things that only occur once
    • shouldn’t be used because of ease of use
    • testing can be harder, but you can make a base class which the singleton inherits from and let the test use the base class
  13. Factory
    • delegate the “which class” decision
    • abstract factory can return a factory
    • common issue is using when you don’t need it
    • technique is useful when you have a choice of several different classes and you need to pick one
  14. Builder
    • sometimes configuring an object is difficult
    • make sure not to return references to the same object
    • might be good if seeing object creation logic repeated or producing invalid objects
    • default to using new until compelling reason
  15. Interpreter
    • some programming problems best solved by creating a specialized langauge
    • good if crisp boundaries or lots of discrete code chunks you find yourself combining in various ways
    • work on abstract syntax tree
    • can be complex / slow
    • can be good for queries or configs
  16. Domain-Specific Languages
    • focuses on the language not the interpreter
    • sometimes problem can be solved by providing a convenient syntax
    • external DSL: requires parser
    • internal DSL: start with a language and bend to become DSL i.e. using Ruby
    • building in ruby allows you to leverage language capabilities
    • limited to language
    • error messages can be hard to interpret
  17. Meta-programming
    • in dynamic language can modify singleton object and customize
    • can use Object or some base class with some base functionality
    • can also define modules and extend the object with appropriate modules
    • can create custom methods with class_eval
    • reflection features let you learn about the object and available methods etc
    • the history of the object may matter more than the class
    • meta-programming makes the running code look different from the source
    • can be much harder to debug
    • tests are vital
    • attr_accessor/attr_reader/attr_writer and the Forwardable module are examples
  18. Convention Over Configuration
    • concerned with pulling together whole applications
    • how to structure to make extensible
    • code can become dependent on configuration
    • some good guidelines
      • attempt to anticipate the user’s needs
      • don’t make the user repeat himself or herself
      • provide a starter template
    • make what the user wants to do a lot default/easy
    • if we follow a convention e.g. name class <protocol>Adapter, the system can pick up by name
    • if we have a directory convention, things can be auto-loaded / required
    • too many conventions can make it seem like magic
    • needs good documentation and thorough unit tests
    • can be very confusing if inconsistent or broken conventions

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s