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

results matching ""

    No results matching ""