Design Patterns

SOLID

S

Single responsibility

O

Open-Closed principle

Class shoul be open to extensions and closed for modifications

LSP

Liskov substitution prinsiple

E.g.

  • If type A have set of abilities
  • {|a| a -> A}
  • If type B have set of abilities {|b| b -> B}
  • And B is subtype of A
  • than this {|b| b -> A}
  • shoud not altering A

I

Interface segregation

No client should be forced to depend on methods it does not use.

class FeeCalculator
  def calculate(product, user, vat, save_result)
    # calculation

    if save_result
      # storing result into db
    end
  end
end

# better

class FeeCalculator
  def calculate(product, user, vat)
    # calculation
  end

  def save(fee)
    # storing result into db
  end
end

D

Dependency inversion

The conventional dependency relationships established from high-level, policy-setting modules to low-level, dependency modules are reversed.

Thus rendering high-level modules independent of the low-level module implementation details.

E.g.

  • This
    class UserRepository
      def all
        User.all
      end
    end
    
  • Should be rewritten to this
    class UserRepository
      def initialize(data_source:)
        @data_source = data_source
      end
    
      def all
        data_source.all
      end
    end
    

The principle states:

  • High-level modules should not depend on low-level modules. Both should depend on abstractions.
  • Abstractions should not depend on details. Details should depend on abstractions.

Law of Demeter

E.G. Do not talk to stranger

Details

A.new.method1.method2.method3

In this case you allowed to use:

  • A and its instance methods
  • method1's parameters
  • Any objects crrated/instantiated within method1
  • You should change method1 definition or change public interface at all

Design Patterns

Singleton

Implementation

This ensures that only one instance of Klass can be created.

class Klass
   include Singleton
   # ...
end


a,b  = Klass.instance, Klass.instance

a == b
# => true

Klass.new
# => NoMethodError - new is private

Multiton

Multiton design pattern ensures only one object is allocated for a given state.

The 'multiton' pattern is similar to a singleton, but instead of only one instance, there are several similar instances.

Implementation

class SomeMultitonClass
  include Multiton
  attr :arg
  def initialize(arg)
    @arg = arg
  end
end

a = SomeMultitonClass.new(4)
b = SomeMultitonClass.new(4)   # a and b are same object
c = SomeMultitonClass.new(2)   # c is a different object

Info

It is useful when you want to avoid constructing objects many times because of some huge expense (connecting to a database for example), require a set of similar but not identical objects, and cannot easily control how many times a contructor may be called.

Strategy

“Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.”

Implementation

class Car
  def initialize(engine)
    @engine = engine
  end
end

# Engine strategy 1
class EngineOne
  def start
  end

  def stop
  end
end

# Engine strategy 2
class EngineTwo
  def start
  end

  def stop
  end
end

# Varying the engine algorithm
car_a = Car.new(EngineOne.new)
car_b = Car.new(EngineTwo.new)

Info

  • The strategies share an interface When using the Strategy pattern you should be sure to couple clients to the interface shared by each of the strategies, and not to any concrete strategy. In Ruby, which lacks formal interfaces, this merely means that each strategy in the family of algorithms share the same public API (the same public methods).
  • Getting rid of if statements The Strategy pattern is often used to eliminate conditional statements within a class. Sometimes, when you have different algorithms for a desired behavior and the algorithm is chosen conditionally, it makes sense to encapsulate those algorithms into classes (strategies) and eliminate the conditionals altogether.
  • Composition over Inheritance Because the Strategy pattern uses composition instead of inheritance, it is often considered superior to solutions favoring inheritance.

Observer

The observer pattern is used when you are building a system where the state of one object effects the state of other objects. The Observer pattern (also known as publish/subscribe) provides a simple mechanism for one object to inform a set of interested third-party objects when its state changes.

Stdlib ruby implementation

Ticker

require "observer"

class Ticker          ### Periodically fetch a stock price.
  include Observable

  def initialize(symbol)
    @symbol = symbol
  end

  def run
    lastPrice = nil
    loop do
      price = Price.fetch(@symbol)
      print "Current price: #{price}\n"
      if price != lastPrice
	changed                 # notify observers
	lastPrice = price
	notify_observers(Time.now, price)
      end
      sleep 1
    end
  end
end

Action

class Price           ### A mock class to fetch a stock price (60 - 140).
  def Price.fetch(symbol)
    60 + rand(80)
  end
end

class Warner          ### An abstract observer of Ticker objects.
  def initialize(ticker, limit)
    @limit = limit
    ticker.add_observer(self)
  end
end

class WarnLow < Warner
  def update(time, price)       # callback for observer
    if price < @limit
      print "--- #{time.to_s}: Price below #@limit: #{price}\n"
    end
  end
end

class WarnHigh < Warner
  def update(time, price)       # callback for observer
    if price > @limit
      print "+++ #{time.to_s}: Price above #@limit: #{price}\n"
    end
  end
end

ticker = Ticker.new("MSFT")
WarnLow.new(ticker, 80)
WarnHigh.new(ticker, 120)
ticker.run

Implementation

class Publisher
  attr_reader :message

  def initialize
    @subscribers = []
  end

  def message=(new_message)
    @message = new_message
    notify
  end

  def create_subscription(subscriber)
    @subscribers.push(subscriber)
  end

  def cancel_subscription(subscriber)
    @subscribers.remove(subscriber)
  end

  private

  def notify
    @subscribers.each { |subscriber| subscriber.update(message) }
  end
end

class Subscriber
  def update(changes)
    puts "Hey! Here are changes: #{changes}"
  end
end

subscriber_first = Subscriber.new
subscriber_second = Subscriber.new
publisher = Publisher.new

publisher.create_subscription(subscriber_first)
publisher.create_subscription(subscriber_second)

# Let's give the subject a name
publisher.message = 'Hello!'
publisher.message = 'New message'

Template Method

“Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure.”

Implementation

class AndroidTemplate
  def build_android
    assemble_body
    add_arm
    add_leg
    add_cpu
    add_eyes
    add_tattoo
  end

  def add_cpu
    raise "Called abstract method: add_cpu"
  end
end

class AndroidBob < AndroidTemplate
  def add_cpu
    @cpu = Corei7.new
  end
end

Info

  • It uses inheritance
  • Rise exeptions to mimik adstract methods

Flyweight

Implementation

class Tree
  attr_reader :name

  def initialize(name)
    @name = name
  end
end

class TreeFactory
  attr_reader :pool
  def initialize
    @pool = {}
  end

  def find_tree(name)
    # if the tree already exists, reference it instead of creating a new one
    if @pool.key?(name)
      tree = @pool[name]
    else
      tree = create_tree(name)
      @pool[name] = tree
    end
    tree
  end

  def pool_size
    @pool.size
  end

  private

  def create_tree(tree)
    Tree.new(tree)
  end
end

Implementation cont.

class Wood
  attr_reader :tree_factory

  def initialize
    @tree_factory = TreeFactory.new
    @wood = []
  end

  def add_tree(tree)
    tree = @tree_factory.find_tree(tree)
    @wood << tree
  end

  def pool
    @tree_factory.pool
  end

  def result
    @wood.map(&:name)
  end
end

And run

wood = Wood.new
trees = ["oak", "baobab", "oak", "baobab"]

p 'original'
puts trees.map(&:object_id) # =>[47170809134420, 47170809134400, 47170809134380, 47170809134360]


trees.each do |tree|
  wood.add_tree(tree)
end

p 'After'
p wood.result # => [47170809134420, 47170809134400, 47170809134380, 47170809134360]

p wood.tree_factory.pool_size # => 2
p wood.pool # => {"oak"=>#<Tree:0x000055cd9ea4f760 @name="oak">, "baobab"=>#<Tree:0x000055cd9ea4f738 @name="baobab">}

When to use

According to the GoF, one should apply the Flyweight ib such cases:

  • An application uses a large number of objects.
  • Storage costs are high because of the sheer quantity of objects.
  • Most object state can be made extrinsic.
  • Many groups of objects may be replaced by relatively few shared objects once extrinsic state is removed.
  • The application doesn’t depend on object identity. Since flyweight objects may be shared, identity tests will return true for conceptually distinct objects.

Abstract Factory

“Provide an interface for creating families of related or dependent objects without specifying their concrete classes.”

Implementation

class RobotFactory
  def initialize(robot)
    @robot = robot.new
  end

  def create
    @robot.add_legs
    @robot.add_arms
    @robot
  end
end

Implementation cont.

class HumanoidRobot
  attr_accessor :legs, :arms

  def add_legs
    legs = HunanoidLegs.create
  end

  def add_arms
    arms = HumanoidArms.create
  end
end

Implementation cont.

class SpiderRobot
  attr_accessor :legs, :arms

  def add_legs
    legs = SpigerLegs.create
  end

  def add_arms
    arms
  end
end

Run

r_factory = RobotFactory.new
android  = r_factory.create(HumanoidRobot)
spider_robot = r_factory.create(SpiderRobot)

Info

Composition vs Inheritance

As with the Strategy pattern, composition is used to decouple set of compatible objects, what those objects are and how those objects are created, from the class that uses those objects.

When to use the Abstract Factory pattern

  • You should use the Abstract Factory pattern whenever you have a system which should remain independent of how its objects are created, composed, and represented.
  • You should also use the Abstract Factory pattern in cases where related families of objects are created and designed to be used together and you need to enforce that design.
  • The Abstract Factory pattern may also be used to reveal the interfaces of families of objects, but conceal their implementations.

Benefits of the Abstract Factory pattern

The Abstract Factory pattern makes it easy to create and manage families of objects. It also makes it easy to to exchange one family of objects for another, as they share an interface. It promotes consistency across the product families, leading to a pleasing, though more rigid, design.

Builder

“Separate the construction of a complex object from its representation so that the same construction process can create different representations.”

Implementation

class AndroidBuilder
  attr_reader :android

  def initialize
    @android = Android.new
  end

  def android
    raise "too few legs" if @android.number_of_legs < 2
    raise "too many legs" if @android.number_of_legs > 20
    raise "improper body" if @android.body.nil?
    raise "no cpu" if @cpu.nil?
    @android
  end

  def select_number_of_legs(number)
    @android.build_legs(number)
  end

  def configure_legs(leg_size)
    @android.legs.each do |leg|
      leg.size = leg_size
    end
  end

  def make_body(material)
    @android.body = AndroidBody.new(material)
  end

  def insert_cpu(cpu_type)
    @android.cpu = Cpu.new(cpu_type)
  end
end

Implementation cont.

class Android
  attr_accessor :body, :legs, :cpu

  def initialize
    @legs = []
  end

  def number_of_legs
    @legss.length
  end

  def build_legs(number)
    number.times do
      @legs.push(AndroidLeg.new)
    end
  end
end

class AndroidLeg
  attr_accessor :size
end

class AndroidBody
  attr_accessor :material
  def initialize(material)
    @material = material
  end
end

class Cpu
  attr_accessor :type
end

And run

android_builder = AndroidBuilder.new
android_builder.select_number_of_legs(3)  
android_builder.configure_legs(32)
android_builder.make_body('carbon')
android_builder.insert_cpu('Intel Atom')

android = android_builder.android

Info

  • Builder vs factories A builder class is primarily intent on configuring an object. A factory class (i.e. Abstract Factory) is primarily intent on creating an object.
  • Enforcing valid objects A builder can be used to enforce business rules for a particular object by raising an error, upon retrieval of the object being built, if the object has not been fully or properly configured.

Prototype

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.

Implementation

class Sheep
  def initialize(params={})
     @name = params.fetch(:name, nil)
     @codename = params.fetch(:codename, nil)
  end

  def _clone(codename)
    Sheep.new(
      codename: self.codename(codename),
      name: self.name
    )
  end

  def codename(codename)
    codename ? @codename = codename : codename
  end

  def name
    "Betty"
  end
end

sheep_prototype = Sheep.new
sheep_instance1 = sheep_prototype._clone("007")
sheep_instance2 = sheep_prototype._clone("3312")

p sheep_instance1 #<Sheep:0x000055b2aac250d0 @name="Betty", @codename="007">
p sheep_instance2 #<Sheep:0x000055b2aac25030 @name="Betty", @codename="3312">

Decorator

Attach additional responsibilities to an object dynamically.

Decorators provide a flexible alternative to subclassing for extending functionality

Implementation

class C
  attr_accessor :lang

  def lang
    "C"
  end
end

class Cpp
  def initialize(c)
    @c = c
  end

  def lang
    @c.lang + '++'
  end
end

class Ruby
  def initialize(c)
    @c = c
  end

  def lang
    'More fun on top of '+ @c.lang 
  end

  def lamdas
    ->(){"I am lambda"}
  end
end

c = C.new
cpp = Cpp.new(c)
ruby = Ruby.new(c)

Advanteges

  • Simple to understand new system
  • Simple to organize system
  • One 'language' for programmers

Disadvanteges

  • Mostly for OOP langs
  • Suit for common patterns of building software

Fun

Objects have failed

http://www.dreamsongs.com/Files/ObjectsHaveFailed.pdf

TDLR

  • The article about implementation of OOP in Java/C++/C# and derivates.
  • And why this impl is not perfect.
  • It is primarily about OOP implementation.
  • E.g. shared state, hard to test, hard to concurent or paraller programms.

Alexander Stepanov

  • Author of C++ STL(Standart Template Library) http://www.stlport.org/resources/StepanovUSA.html

    I find OOP methodologically wrong. It starts with classes.

    It is as if mathematicians would start with axioms. You do not start with axioms - you start with proofs. Only when you have found a bunch of related proofs, can you come up with axioms. You end with axioms.

The same thing is true in programming: you have to start with interesting algorithms. Only when you understand them well, can you come up with an interface that will let them work.

Object have not failed

http://www.dreamsongs.com/ObjectsHaveNotFailedNarr.html

Clojure Design Patterns

http://mishadoff.com/blog/clojure-design-patterns/