Skip to content

Instantly share code, notes, and snippets.

@martindemello
Created February 20, 2015 21:30
Show Gist options
  • Save martindemello/7231bf0f407ca428b509 to your computer and use it in GitHub Desktop.
Save martindemello/7231bf0f407ca428b509 to your computer and use it in GitHub Desktop.
chain of responsibility example in ruby
class PurchaseApprover
# Implements the chain of responsibility pattern. Does not know anything
# about the approval process, merely whether the current handler can approve
# the request, or must pass it to a successor.
attr_reader :successor
def initialize successor
@successor = successor
end
def process_request request
if approve_request request
return
elsif successor
successor.process_request request
else
deny_request request
end
end
# This may be overridden by a handler if it wants to provide a custom action
# when it is the last member of the chain
def deny_request request
puts "Your request for $#{request.amount} needs a board meeting!"
end
end
class AmountApprover < PurchaseApprover
# Base class for approvers who only consider whether the request amount is
# allowable
BASE = 500
def approve_request request
if request.amount < self.class::ALLOWABLE
print_approval request
return true
else
return false
end
end
end
class Manager < AmountApprover
ALLOWABLE = 10 * BASE
def print_approval request
puts "Manager will approve $#{request.amount}"
end
end
class Director < AmountApprover
ALLOWABLE = 20 * BASE
def print_approval request
puts "Director will approve $#{request.amount}"
end
end
class VicePresident < AmountApprover
ALLOWABLE = 40 * BASE
def print_approval request
puts "VicePresident will approve $#{request.amount}"
end
end
class President < AmountApprover
ALLOWABLE = 60 * BASE
def print_approval request
puts "President will approve $#{request.amount}"
end
end
class CFO < PurchaseApprover
# An example of a handler that does not inherit from AmountApprover
#
# Try adding it to the chain by implementing an annual budget that tracks the
# total of all approved requests so far and whether we can afford one more.
def approve_request request
if within_annual_budget? request
puts "CFO will approve $#{request.amount}"
return true
else
return false
end
end
def within_annual_budget? request
# ...
end
end
class PurchaseRequest
attr_reader :amount
def initialize(number, amount, purpose)
@number = number
@amount = amount
@purpose = purpose
end
end
class CLP
def initialize(*approvers)
approvers = build_approvers(approvers)
@authority = approvers.first
end
def process_request request
@authority.process_request request
end
private
def build_approvers(approver_classes)
[].tap do |approvers|
approver_classes.reverse.inject(nil) do |successor, approver|
approver.new(successor).tap {|approver| approvers.unshift approver }
end
end
end
end
# main program
# ------------
approvers = CLP.new(Manager, Director, VicePresident, President)
loop do
$stdout.flush
puts
puts "Enter the amount to check who should approve your expenditure."
print ">> "
amount = gets.to_i
approvers.process_request PurchaseRequest.new(0, amount, 'General')
end
@malachaifrazier
Copy link

Well done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment