Class shoul be open to extensions and closed for modifications
A
have set of abilities{|a| a -> A}
{|b| b -> B}
B
is subtype of A
{|b| b -> A}
A
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
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. Do not talk to stranger
A.new.method1.method2.method3
In this case you allowed to use:
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 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.
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
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.
“Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.”
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)
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.
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
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
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'
“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.”
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
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
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
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">}
According to the GoF, one should apply the Flyweight ib such cases:
“Provide an interface for creating families of related or dependent objects without specifying their concrete classes.”
class RobotFactory
def initialize(robot)
@robot = robot.new
end
def create
@robot.add_legs
@robot.add_arms
@robot
end
end
class HumanoidRobot
attr_accessor :legs, :arms
def add_legs
legs = HunanoidLegs.create
end
def add_arms
arms = HumanoidArms.create
end
end
class SpiderRobot
attr_accessor :legs, :arms
def add_legs
legs = SpigerLegs.create
end
def add_arms
arms
end
end
r_factory = RobotFactory.new
android = r_factory.create(HumanoidRobot)
spider_robot = r_factory.create(SpiderRobot)
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.
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.
“Separate the construction of a complex object from its representation so that the same construction process can create different representations.”
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
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
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
Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
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">
Attach additional responsibilities to an object dynamically.
Decorators provide a flexible alternative to subclassing for extending functionality
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)
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.