├── .gitignore ├── LICENSE.md ├── README.md ├── code_examples ├── inheritance-vs-composition.rb ├── strategy.rb └── template.rb ├── concepts └── inheritance-vs-composition.md └── patterns ├── observer.md ├── strategy.md └── template_method.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pruett/ruby-patterns/1ce3c52e9a80a09309163434cac43813fcecdf6f/LICENSE.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :boom: NO LONGER MAINTAINED :boom: 2 | 3 | Design Patterns in Ruby 4 | ======================= 5 | 6 | ## What is a design pattern? 7 | 8 | Design patterns provide solutions to commonly occurring problems. "[Software design] Patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system." [1][wiki-design-patterns] 9 | 10 | While the techniques, solutions, and best practices highlighted here are rooted in the Ruby language, the intention is to provide utility to any software developer writing in any language. 11 | 12 | [wiki-design-patterns]: http://en.wikipedia.org/wiki/Software_design_pattern "Software design pattern" 13 | 14 | ## Inspiration 15 | 16 | This is 100% inspired by the concepts outlined in Russ Olsen's excellent [Design Patterns In Ruby](http://www.amazon.com/Design-Patterns-Ruby-Russ-Olsen/dp/0321490452) book. I highly recommend picking it up. 17 | 18 | ## Table of Contents 19 | 20 | ### Concepts 21 | 22 | [Inheritance vs. Composition](/concepts/inheritance-vs-composition.md) 23 | 24 | ### Patterns 25 | 26 | 1. [Template Method Pattern](/patterns/template_method.md) 27 | 2. [Strategy Pattern](/patterns/strategy.md) 28 | 3. [Observer Pattern](/patterns/observer.md) 29 | 4. Composite Pattern 30 | 5. Iterator Pattern 31 | 6. Command Pattern 32 | 7. Adapter Pattern 33 | 8. Proxy Pattern 34 | 9. Decorator Pattern 35 | 10. Singleton Pattern 36 | 11. Factory Pattern 37 | 12. Builder Pattern 38 | 13. Interpreter Pattern 39 | 40 | ## Contributing 41 | 42 | Feel free to contribute if you find any glaring mistakes, poor code examples, or anything else that needs fixing. 43 | 44 | #### To submit pull request 45 | + Fork this repo 46 | + Commit changes on a feature branch 47 | + Submit pull request [your-feature-branch -> pruett/ruby-patterns/master] 48 | 49 | Or simply open up an issue. 50 | 51 | Thanks! 52 | -------------------------------------------------------------------------------- /code_examples/inheritance-vs-composition.rb: -------------------------------------------------------------------------------- 1 | class A 2 | def hello; puts "hello"; end 3 | def goodbye; puts "goodbye"; end 4 | end 5 | 6 | class B < A 7 | def yo; puts "yo"; end 8 | end 9 | 10 | A.new.hello 11 | B.new.hello 12 | B.new.yo 13 | # A.new.yo 14 | 15 | class Mobility 16 | def lumber; puts "lumbering"; end 17 | def crabwalk; puts "crabwalking"; end 18 | end 19 | 20 | class Bear 21 | def initialize 22 | @mobility = Mobility.new 23 | end 24 | 25 | def move 26 | @mobility.lumber 27 | end 28 | end 29 | 30 | class Crab 31 | def initialize 32 | @mobility = Mobility.new 33 | end 34 | 35 | def move 36 | @mobility.crabwalk 37 | end 38 | end 39 | 40 | Bear.new.move 41 | Crab.new.move 42 | -------------------------------------------------------------------------------- /code_examples/strategy.rb: -------------------------------------------------------------------------------- 1 | # class Hair 2 | # attr_reader :color, :texture, :length 3 | 4 | # def initialize(product) 5 | # @color = "brown" 6 | # @texture = "wavy" 7 | # @length = "short" 8 | # @product = product 9 | # end 10 | 11 | # def apply 12 | # @product.apply(self) 13 | # end 14 | # end 15 | 16 | # class Shampoo 17 | # def apply(context) 18 | # puts "washing the #{context.color}, #{context.texture} hair...}" 19 | # end 20 | # end 21 | 22 | # class Conditioner 23 | # def apply(context) 24 | # product = determine_conditioner(context) 25 | # puts "since this hair is #{context.length}, we'll have to use #{product}" 26 | # end 27 | 28 | # def determine_conditioner(context) 29 | # context.length == "short" ? "Brand A" : "Brand B" 30 | # end 31 | # end 32 | 33 | # hair = Hair.new(Shampoo.new) 34 | # hair.apply 35 | 36 | # hair = Hair.new(Conditioner.new) 37 | # hair.apply 38 | 39 | class Hair 40 | attr_reader :color, :texture, :length 41 | 42 | def initialize(&block) # accepts a code block 43 | @color = "brown" 44 | @texture = "wavy" 45 | @length = "short" 46 | @block = block 47 | end 48 | 49 | def apply 50 | @block.call(self) # calls code block with reference to itself 51 | end 52 | end 53 | 54 | shampoo = Hair.new { |context| puts "washing #{context.color} hair..." } 55 | shampoo.apply #=> washing brown hair... 56 | 57 | conditioner = Hair.new do |context| 58 | puts "since this hair is #{context.length}," 59 | puts "we'll have to use" 60 | puts "#{context.length == "short" ? "Brand A" : "Brand B"}" 61 | end 62 | conditioner.apply #=> since this hair is short, \n we'll have to use \n Brand A 63 | -------------------------------------------------------------------------------- /code_examples/template.rb: -------------------------------------------------------------------------------- 1 | class Template 2 | def apply 3 | start 4 | first 5 | second 6 | third 7 | finish 8 | end 9 | 10 | def first 11 | raise 'must implement first() method' 12 | end 13 | 14 | def second 15 | raise 'must implement second() method' 16 | end 17 | 18 | def third 19 | raise 'must implement third() method' 20 | end 21 | 22 | def start; end 23 | 24 | def finish; end 25 | end 26 | 27 | class Shampoo < Template 28 | def start 29 | puts "rinse hair" 30 | end 31 | 32 | def first 33 | puts "apply shampoo" 34 | end 35 | 36 | def second 37 | puts "clean hair" 38 | end 39 | 40 | def third 41 | puts "rinse shampoo from hair" 42 | end 43 | end 44 | 45 | class Conditioner < Template 46 | def first 47 | puts "apply conditioner" 48 | end 49 | 50 | def second 51 | puts "condition hair" 52 | end 53 | 54 | def third 55 | puts "rinse conditioner from hair" 56 | end 57 | 58 | def finish 59 | puts "you hair is now silky and smooth" 60 | end 61 | end 62 | 63 | # Shampoo.new.apply 64 | Conditioner.new.apply 65 | -------------------------------------------------------------------------------- /concepts/inheritance-vs-composition.md: -------------------------------------------------------------------------------- 1 | Inheritance Versus Composition 2 | ============================== 3 | 4 | ## Inheritance 5 | 6 | **Inheritance** generally describes an *is a kind of* relationship between objects. In other words, through **inheritance**, one object is based off of another. This hierarchical implementation acts as an effective mechanism for code reuse. 7 | 8 | ```ruby 9 | class A 10 | def hello; puts "hello"; end 11 | def goodbye; puts "goodbye"; end 12 | end 13 | 14 | class B < A # B inherits from A 15 | def yo; puts "yo"; end 16 | end 17 | 18 | A.new.hello #=> "hello" 19 | B.new.hello #=> "hello" 20 | B.new.goodbye #=> "goodbye" 21 | B.new.yo #=> "yo" 22 | A.new.yo #=> undefined method `yo' 23 | ``` 24 | 25 | We declare two class objects, `A` and `B`. We define `B` as a **subclass** of a `A`. This provides `B` with all of the behavior of `A`. In our case, `B` *inherits* both the `hello` and `goodbye` methods from its **superclass** `A` automatically. 26 | 27 | While inheritance shines in its ability to extend objects effortlessly, maintain code hierarchy, and [DRY](http://en.wikipedia.org/wiki/Don't_repeat_yourself) up code, there is no denying the tight coupling that exists between the `A` and `B` class objects. 28 | 29 | For instance, imagine `A`'s behavior evolving over time, and how it could affect any its **subclasses**. This is a something to constantly consider when dealing with inheritance. A simple change in a **superclass**'s functionality could trickle down and break functionality in a **subclass**. This is where inheritance starts to decay. 30 | 31 | ## Composition 32 | 33 | **Composition** provides an alternative to inheritance. It typically illustrates a *has a* relationship between objects. 34 | 35 | Composition aims at solving the pitfalls of inheritance through *encapsualtion*, *de-coupling*, and *delegation*. Instead of the tightly coupled objects sometimes created as a result inheritance, composition allows us to keep objects independent of each other, without fear of indirectly affecting dependent objects. 36 | 37 | ```ruby 38 | class Mobility 39 | def lumber; puts "lumbering"; end 40 | def crabwalk; puts "crabwalking"; end 41 | end 42 | 43 | class Bear 44 | def initialize 45 | @mobility = Mobility.new 46 | end 47 | 48 | def move 49 | @mobility.lumber 50 | end 51 | end 52 | 53 | class Crab 54 | def initialize 55 | @mobility = Mobility.new 56 | end 57 | 58 | def move 59 | @mobility.crabwalk 60 | end 61 | end 62 | 63 | Bear.new.move #=> lumbering 64 | Crab.new.move #=> crabwalking 65 | ``` 66 | 67 | Above, we define a `Mobility` class, with several methods, which we'll use to compose objects with. `Bear` and `Crab` both *inherit* the `Mobility` object by instantiating an instance of `Mobility` within their respective `intialize` methods. From here, they are able to use all of `Mobility`'s capabilities as they see fit...without the confines of traditional inheritance depicted above. 68 | 69 | Notice we have solid **encapsulation** in our class objects by avoiding any direct inheritance. It is `Mobility`'s responsibility to provide a clean interface, as it's `Bear` and `Crab`'s duties to properly interact with it. The objects are fully **de-coupled** and instances of `Bear` and `Crab` **delegate** responsibility onto `Mobility`. 70 | 71 | ## Inheritance vs. Composition 72 | 73 | The question of whether to use inheritance or composition typically depends on the situation. Both serve similar functions, but result in very different outcomes. 74 | 75 | If you have a simple object hierarchy that answers yes to the question: "is *y* a kind of *x*?", you will likely first abstract the essence of *x* and create subclasses that inherit its behavior. Inheritance shines in simple examples that follow a disciplined path -- non-complex object relationships that build nicely off a defined superclass. 76 | 77 | If the relationship is less hierarchical, however, reaching for composition is usually a good bet. Composition tends to provide more flexibility and extensibility, especially when attempting to model your code around a future which is largely unknown and in constant flux. 78 | -------------------------------------------------------------------------------- /patterns/observer.md: -------------------------------------------------------------------------------- 1 | Observer Pattern 2 | ================ 3 | 4 | There may be scenarios in which you have an object that triggers the actions of third-party objects. The *Observer pattern* (commonly referred as *publish/subscribe* or *pubsub* for short) is designed to effectively handle these types of circumstances in a manageable way. 5 | 6 | Let's consider the following example: 7 | 8 | **When Tom turns on the shower, he would like:** 9 | + the coffee pot to brew coffee 10 | + the tv to tune to a particular show 11 | + his sonos sound system to play on some tunes 12 | 13 | ## First Attempt 14 | 15 | Semi-psuedo code for a situation above **could** look like the following: 16 | 17 | ```ruby 18 | class HomeOwner 19 | def initialize(name, fav_tv_station, fav_band, fav_coffee_type, coffeepot, television, sonos) 20 | @name = name 21 | @tv_station = tv_station 22 | ... # initialize rest of args as instance variables 23 | end 24 | 25 | def turn_on_shower 26 | @coffeepot.brew(@coffee_type) 27 | @television.tune(@tv_station) 28 | @sonos.shuffle(@fav_band) 29 | end 30 | end 31 | 32 | tom = HomeOwner.new('tom', 'sportscenter', 'Beatles, The', 'decaf', CoffeePot.new, Television.new, Sonos.new) 33 | tom.turn_on_shower 34 | ``` 35 | 36 | Simple right? All the desired objects are triggered when `Homeowner#turn_on_shower` is invoked... 37 | 38 | Well, not so fast...there is a much more flexible way in keeping modeling this behavior. 39 | 40 | ## Utilizing the *Observer pattern* 41 | 42 | ### De-couple Observers from the Subject 43 | 44 | In the above example, `HomeOwner` is our **subject**. It provides the stimulus or action which triggers other objects to react. These reactionary objects are the **observers** in the equation. The **subject** publishes an update, and the **observers** listen and spring into action when they hear the trigger. 45 | 46 | Our first order of business is to separate out the **observers** from the `turn_on_shower` method. We'll keep them in an array that's instantiated (empty by default) when we create the **subject**. Below is our refactored `initialize` method: 47 | 48 | ```ruby 49 | # home_owner.rb 50 | ... 51 | 52 | def initialize(name, fav_tv_station, fav_band, fav_coffee_type) 53 | # Initialize instance variables as usual 54 | ... 55 | # Initialize an empty array to hold observers 56 | @observers = [] 57 | end 58 | ``` 59 | 60 | In addition to providing an array to store the **observers**, we'll have to add a few more utility methods to add/remove/call them. 61 | 62 | ```ruby 63 | # home_owner.rb 64 | ... 65 | 66 | # Add/Remove observers 67 | 68 | def add_observers(observers) 69 | observers.each do |observer| 70 | @observers << observer 71 | end 72 | end 73 | 74 | def delete_observer(observer) 75 | @observers.delete(observer) 76 | end 77 | 78 | # Update trigger method 79 | 80 | def turn_on_shower 81 | notify_observers 82 | end 83 | 84 | # Define the execution 85 | 86 | def notify_observers 87 | @observers.each do |observer| 88 | observer.fire(self) 89 | end 90 | end 91 | ``` 92 | 93 | Now, any `HomeOwner` can register observers like so: 94 | 95 | ```ruby 96 | # Define HomeOwner and their interests 97 | tom = HomeOwner.new('tom', 'sportscenter', 'Beatles, The', 'decaf') 98 | mary = HomeOwner.new('mary', 'news', 'Coldplay', nil) 99 | 100 | # Initialize observers 101 | coffeepot, television, sonos = CoffeePot.new, Television.new, Sonos.new 102 | 103 | # Add observers 104 | tom.add_observers([coffeepot, television, sonos]) 105 | mary.add_observers([television, sonos]) 106 | ``` 107 | 108 | This technique decouples **observers** from their respective **subject**, affording us the ability to define multiple `HomeOwner` instances, each with their own set of observers. We may define many, few, or even no observers and it won't matter one bit. 109 | 110 | ### Observer Interface 111 | 112 | You may have noticed in our `noftify_observers` method that we call each **observer** with a `fire` call and pass in an instance of `self`. We can call this method anything, I chose `fire`, but it's important to remember that each **observer** will need to respond to this method. So, for example, the `CoffeePot` class could look like the following: 113 | 114 | ```ruby 115 | class CoffeePot 116 | def fire(homeowner) 117 | brew homeowner.fav_coffee_type 118 | end 119 | 120 | def brew(coffee) 121 | # instructions for brewing coffee 122 | end 123 | end 124 | ``` 125 | 126 | ### Extract Observable into Module 127 | 128 | Now that we have comprised a solid observable pattern, we can go ahead and wrap it up in a module so we can mix it in to any future **subject**. In fact, Ruby has already done this for us with its [Observable Module](http://ruby-doc.org/stdlib-1.9.3/libdoc/observer/rdoc/Observable.html#method-i-delete_observers) which works very similar to what we have explained above. In order to use the module, we would do something like: 129 | 130 | ```ruby 131 | require 'observer' 132 | 133 | class HomeOwner 134 | # Declare HomeOwner as a subject, providing 135 | # core observability functionality 136 | include Observable 137 | 138 | ... 139 | # rest of class implementation 140 | end 141 | 142 | class CoffeePot 143 | def update 144 | # all observers called by their update method 145 | end 146 | end 147 | ... 148 | ``` 149 | 150 | The *Observer pattern* is nice way to keep **observers** and their **subjects** from tangling up. It is important to keep in mind the interface between the two and the level of complexity that should exist. 151 | -------------------------------------------------------------------------------- /patterns/strategy.md: -------------------------------------------------------------------------------- 1 | Strategy Pattern 2 | ================ 3 | 4 | The *Strategy pattern* provides similar utility as the [Template Method pattern](/patterns/template_method.md), but instead chooses a [composition](/concepts/inheritance-vs-composition.md#composition)-based approach. 5 | 6 | We'll continue where we left off with the [Template Method pattern](/patterns/template_method.md) example, but now, instead of creating `Shampoo` and `Conditioner` as subclasses, we'll define them as **strategies**. 7 | 8 | ```ruby 9 | class Shampoo 10 | def apply(context) 11 | puts "washing the #{context.color}, #{context.texture} hair..." 12 | end 13 | end 14 | 15 | class Conditioner 16 | def apply(context) 17 | product = determine_conditioner(context) 18 | puts "since this hair is #{context.length}, we'll have to use #{product}" 19 | end 20 | 21 | def determine_conditioner(context) 22 | context.length == "short" ? "Brand A" : "Brand B" 23 | end 24 | end 25 | ``` 26 | 27 | What makes these POROs (Plain Old Ruby Object) **strategies**? Well, nothing *yet*. You'll notice that they do share a common `apply` method and that each has their own unique execution. For now, the details of their execution are not of interest as we still have one more piece to define. 28 | 29 | In addition to individual strategies, the *Strategy pattern* relies what's called a **context** object -- an object that uses strategies. 30 | 31 | Let's define one now. 32 | 33 | ```ruby 34 | class Hair 35 | attr_reader :color, :texture, :length 36 | 37 | def initialize(product) 38 | @color = "brown" 39 | @texture = "wavy" 40 | @length = "short" 41 | @product = product 42 | end 43 | 44 | def apply 45 | @product.apply(self) 46 | end 47 | end 48 | ``` 49 | 50 | We define a `Hair` object which will serve as our **context**. It defines a few instance variables and accepts an argument upon initialization. This argument is the **strategy** being passed through to the **context**. Lastly, you'll see that the context object has an `apply` method of its own, which simply delegates the same call the newly instantiated strategy. 51 | 52 | This is the *Strategy pattern* at work! 53 | 54 | There are a few subtleties in our execution. Namely, when `Hair` delegates the `apply` method to its strategy, it passes an instance of itself along for the strategy to use at will. This is a nice technique that allows the strategy to use it's context as it sees fit, without explicitly having to set the arguments that are passed through. 55 | 56 | Here is the result of our pattern in use: 57 | 58 | ```ruby 59 | shampoo = Hair.new(Shampoo.new) 60 | shampoo.apply #=> washing the brown, wavy hair... 61 | 62 | conditioner = Hair.new(Conditioner.new) 63 | conditioner.apply #=> since this hair is short, we'll have to use Brand A 64 | ``` 65 | 66 | The flexibility of this pattern is showcased if we choose to define our encapsulated strategies, not as classes, but as `Proc` objects instead. 67 | 68 | ```ruby 69 | class Hair 70 | attr_reader :color, :texture, :length 71 | 72 | def initialize(&block) # accepts a code block 73 | @color = "brown" 74 | @texture = "wavy" 75 | @length = "short" 76 | @block = block 77 | end 78 | 79 | def apply 80 | @block.call(self) # calls code block with reference to itself 81 | end 82 | end 83 | 84 | shampoo = Hair.new { |context| puts "washing #{context.color} hair..." } 85 | shampoo.apply #=> washing brown hair... 86 | 87 | conditioner = Hair.new do |context| 88 | puts "since this hair is #{context.length}," 89 | puts "we'll have to use" 90 | puts "#{context.length == "short" ? "Brand A" : "Brand B"}" 91 | end 92 | conditioner.apply #=> since this hair is short, \n we'll have to use \n Brand A 93 | ``` 94 | 95 | By allowing our context to accept code blocks, we can quickly whip up new strategies at runtime without needing to define any additional class objects. Pretty sweet! 96 | 97 | As you can see the *Strategy pattern* maintains a nice separation of concern between the **context** and **strategy** object(s). Since the **context** has no knowledge of how a **strategy** is implemented, the pattern imposes *nearly* no restrictions. 98 | 99 | When implementing the *Strategy pattern* it is important to keep in mind how interfaces play a role. In our example, it was the `apply` method that was critical in making the **context** and **strategy** object(s) communicate. This delegation tradeoff is often a small price to pay compared to a tightly coupled inheritance-based implementation, however. 100 | -------------------------------------------------------------------------------- /patterns/template_method.md: -------------------------------------------------------------------------------- 1 | Template Method Pattern 2 | ======================= 3 | 4 | The *Template Method pattern* is an [inheritance](/concepts/inheritance-vs-composition.md#inheritance)-based approach where a **superclass** defines a set of instructions that are to be customized and executed independently by its **subclasses**. 5 | 6 | ```ruby 7 | class Template 8 | def apply 9 | start 10 | first 11 | second 12 | third 13 | finish 14 | end 15 | 16 | def first 17 | raise 'must implement method: first' 18 | end 19 | 20 | def second 21 | raise 'must implement method: second' 22 | end 23 | 24 | def third 25 | raise 'must implement method: third' 26 | end 27 | 28 | def start; end 29 | 30 | def finish; end 31 | end 32 | ``` 33 | 34 | Above we are defining a class object, `Template`, that holds a few **abstract methods**: `first`, `second`, and `third`. We are telling each of these abstract methods to `raise` an error by default (we want them to be defined/overwritten by a subclass). 35 | 36 | `Template` also defines two more methods, `start` and `finish`, which are optional methods (no explicit `raise`), referred to as **hook** methods. Hook methods provide a way to account for variability between implementations. Lastly, you'll notice that all of these methods are defined within a single skeletal method, a **template method** called `apply`, that executes every method in a particular order. 37 | 38 | ```ruby 39 | class Shampoo < Template 40 | def start 41 | puts "rinse hair" 42 | end 43 | 44 | def first 45 | puts "apply shampoo" 46 | end 47 | 48 | def second 49 | puts "clean hair" 50 | end 51 | 52 | def third 53 | puts "rinse shampoo from hair" 54 | end 55 | end 56 | ``` 57 | 58 | Above, we define a `Shampoo` class that inherits from our `Template` superclass. We override the required, abstract methods, as well as define a hook method, `start`. 59 | 60 | ```ruby 61 | class Conditioner < Template 62 | def first 63 | puts "apply conditioner" 64 | end 65 | 66 | def second 67 | puts "condition hair" 68 | end 69 | 70 | def third 71 | puts "rinse conditioner from hair" 72 | end 73 | 74 | def finish 75 | puts "you hair is now silky and smooth" 76 | end 77 | end 78 | ``` 79 | 80 | Nearly identical implementation pattern as above, `Conditioner` defines its own required abstract methods along with its own hook method, in this case, `finish` 81 | 82 | ```ruby 83 | Shampoo.new.apply 84 | #=> 85 | rinse hair 86 | apply shampoo 87 | clean hair 88 | rinse shampoo from hair 89 | 90 | Conditioner.new.apply 91 | #=> 92 | apply conditioner 93 | condition hair 94 | rinse conditioner from hair 95 | you hair is now silky and smooth 96 | ``` 97 | 98 | The power of the *Template Method Pattern* lies in its simple inheritance-based approach. Once a **subclass** is defined properly, we simply call the **template method**, `apply`, and are afforded the convenience of a customized implementation per **subclass**. 99 | --------------------------------------------------------------------------------