Open viphat opened 6 years ago
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
The intent of the Chain of Responsibility pattern is to:
A Chain of Responsibility is used to decouple senders and receivers by giving multiple objects a chance to handle a request. When using a chain of responsibility, each object maintains a pointer to the successor object in the chain.
The ultimate receiver of the request is referred to as an implicit receiver, as the receiver is not known when the request is sent.. It is not explicitly defined. Each object in the chain of responsibility conforms to a shared interface, called the Handler, which defines an interface for handling requests.
Delivery is not guaranteed
One potential downside of using a chain of responsibility is that, because the receiver of the request is not known (implicit receiver), it is possible that nothing fulfills the request. With a chain of responsibility, delivery of a request is not guaranteed.