Gang of Four Patterns
Creational Patterns
Singleton
A singleton is a class for which only one instance of it exists. It can be useful for establishing a single access point to a limited resource, or creating some sort of connection which you only want one of.
Ruby has a Singleton mixin, which will make the new method private and add a instance method, which will only allow one instance.
require 'singleton'
class Example
include Singleton
def getAccess(val)
puts "Do something here with #{val}"
end
end
ex = Example.instance
#=> #<Example:0x007fa3b4061530>
ex.getAccess('something')
#=> "Do something here with something"
ex2 = Example.instance
#=> #<Example:0x007fa3b4061530> <--- same object as before
The module pattern in JavaScript is another example of a Singleton.
var mod = (function() {
function doThing(){
console.log('do thing');
}
function doAnother(){
console.log('do another');
}
return {
doThing,
doAnother
};
}());
mod.doThing();
// 'do thing'
Factory method
Factories provide you with a simplified interface for creating objects. They abstract away how to create object, but still allow you to specify the type of object you want. They also allow you to easily create multiple objects with the same arguments, or with slightly different arguments.
def UserFactory(type)
case type
when 'admin'
Admin.new({name: "Admin Test", password: "1234", email: "[email protected]"})
when 'user'
User.new({name: "Basic User", password: "1234", email: "[email protected]"})
when 'guest'
Guest.new({name: "Guest User"})
end
end
Structural Patterns
Adapter
An adapter is a simple, intermediary class which will allow one object to use another object the way it wants.
class Thing
def do_some_really_complicated_or_not_complicated_stuff
#some code
end
end
class ThingAdapter
def initialize(thing)
@thing = thing
end
# just simplifying the interface here
def use_thing
@thing.do_some_really_complicated_or_not_complicated_stuff
end
end
class ThingUser
def initialize(adapter)
@adapter = adapter
end
def some_method
#more logic here
@adapter.use_thing
end
end
Composite
Objects whose relationship with other objects form a tree structure can benefit from incorporating the composite patter, as it will unify the interfaces and allow for easy recursive traversal. Tags, Posts, Comments, and Sub-Comments naturally form a tree in a web forum. By giving each methods for adding, removing, and getting children, you make it easier for a Tag, for example, to print out all of its Comments.
class Component
attr_reader :children
def initialize
@children = []
end
def add(component)
@children << component
end
def remove(component)
@children.delete(component)
end
def traverse_children
children.each do |child|
child.traverse_children
p child.to_s
end
end
end
class Tag < Component
def initialize(name)
@name = name
end
end
class Post < Component ...
class Comment < Component ...
tag = Tag.new("GoF")
post = tag.add(Post.new("Composite"))
composite = post.add(Comment.new("Cool stuff"))
...
Decorator
Decorators are used to add additional responsibilities to an object dynamically, instead of adding them to the object itself or through a subclass. They're essentially wrappers that add functionality. They're especially useful in cases when you need to do something unrelated to the business logic of a class or function and don't want to mix the two together, such as logging or sending errors and user tracking information to a server.
class Car
attr_accessor :top_speed
def initialize
@top_speed = 70
end
end
class RaceCar
def initialize(car)
@car = car
end
def top_speed
@car.top_speed + 200
end
end
A special decorator syntax will likely soon be added to JS, and they're already in use in Angular 2, but you can build your own now. They're really just higher order functions.
function doThing(thing) {
console.log('I am a ' + thing);
}
function loggingDecorator(wrappedFn) {
return function(...args) {
console.log('Starting thing');
wrappedFn(...args);
console.log('Ending thing');
}
}
var doIt = loggingDecorator(doThing);
doIt("Plumbus");
// 'Starting thing'
// 'I am a Plumbus'
// 'Ending thing'
Facade
Facades are used to combine multiple interfaces into a single interface. When an object needs to use resources from several different modules, it can be useful to simplify the disparate interfaces into a single class or module.
class UserShowFacade
class << self
def user
@user ||= User.find(15)
end
def notifications
@notificaionts ||= user.notifications
end
def posts
@posts || = Post.all
end
end
end
UserShowFacade.user
UserShowFacade.notifications
UserShowFacade.posts
Behavioral Patterns
Observer
Observers provide Pub/Sub functionality. When an object changes state, a list of third-party objects will be notified of the change. It provides for loose coupling between objects. An object that's being listened to shouldn't be dependent on the listeners.
Ruby provides an Observable mixin. It gives the Observable an add_observer method for adding objects to an array, a changed method for setting the changed state to true, and a notify_observers method for passing arguments to the observers. The observers will need to implement an update method to tell them how to handle changes to the observable.
require "observer"
class ThingToListen
include Observable
def initialize(val)
@val = val
end
def set_val(new_val)
@val = new_val
changed
notify_observers(@val)
end
end
class Listener1
def initialize(thing, maximum)
@maximum = maximum
thing.add_observer(self)
end
def update(val)
if val > @maximum
p "the value is over the maximum"
end
end
end
class Listener2
def initialize(thing, minimum)
@minimum = minimum
thing.add_observer(self)
end
def update(val)
if val < @minimum
p "the value is under the minimum"
end
end
end
thing = ThingToListen.new(88)
Listener1.new(thing, 200)
Listener2.new(thing, 20)
thing.set_val(40)
thing.set_val(300)
#=> "the value is over the maximum"
thing.set_val(10)
#=> "the value is under the minimum"
JavaScript doesn't have a built-in, but it's still easy to implement, as it is in any language.
function ThingToListen(val) {
var observers = [];
function addObserver(obj) {
observers.push(obj);
}
function notifyObservers(...args) {
for (var observer of observers) {
observer.update(...args);
}
}
function setVal(new_val) {
val = new_val;
notify_observers(val);
}
return {
addObserver,
setVal
}
}
...
Template Method
Occasionally, you want to build an algorithm that carries out several steps, but doesn't actually implement every step. You intend for subclasses to implement those missing steps. Template methods create a skeleton of the algorithm, which will defer some steps for an substructure to implement. This allows for separate, decoupled implementations of the algorithm.
class CrazyAlgo
def solve
step1
step2
step3
step4
end
def step1
#calculations
end
def step2
#more calculations
end
def step3
raise "Step3 must be implemented"
end
def step4
#even more calculations
end
end
class CompleteAlgo < CrazyAlgo
def step3
#calculations
end
end
Command
Commands can store information necessary to call different methods at a later time, for example, for queuing or logging requests, or undoing an operation.
class AddCommand
attr_reader :value
def initialize(value)
@value = value
end
def execute(total)
total + @value
end
def undo
total - @value
end
end
class Calculator
attr_reader :value
def initialize
@value = 0
@commands = [];
end
def execute(command)
@commands << command
@value = command.execute(@value)
end
def undo
command = @commands.pop
@value = command.undo(@value)
end
end
calc = Calculator.new
calc.execute(AddCommand.new(55))
calc.execute(AddCommand.new(20))
calc.value #=> 75
calc.undo
calc.value #=> 55
Other GoF Patterns
- Abstract Factory
- Builder
- Prototype
- Bridge
- Flyweight
- Proxy
- Chain of Responsibility
- Interpreter
- Iterator
- Mediator
- Memento
- State
- Strategy
- Visitor