Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain. Please refer refactoring.guru This post will give you code sample with Ruby.
Problem: imagine that we have a recruitment website which allow candidates to submit their CV whether applying a job or building their own profile. Our system will process their CV such as scanning skills in their CV, matching skills with current jobs in our database for finding the best matched jobs, approving the CV for those jobs in order to send it to our employers.
Now let’s create solution for this problem. Note that there are many ways to do same job. This post just shows how to implement chain design pattern in ruby.
What needs to be processed? That’s CV.
class CV
attr_accessor :status, :name
def initialize(status, name)
@status = status
@name = name
end
end
What are handlers?
class CVHandler
include Printable
attr_accessor :status, :next_handler
def initialize(status)
@status = status
end
def handle(cv)
if can_handle?(cv.status)
process(cv)
elsif next_handler
next_handler.handle(cv)
else
puts "Could not handle cv"
end
end
def can_handle?(status)
status <= self.status
end
def process(cv)
raise NotImplementedError
end
end
Now we apply template method pattern. Any new handler will extend above CVHandler class and implement process interface.
class CVUploadHandler < CVHandler
def initialize
super(1)
end
def process(cv)
print(cv.name, 'upload')
end
end
class CVScanHandler < CVHandler
def initialize
super(2)
end
def process(cv)
print(cv.name, 'scan')
end
end
class CVMatchingHandler < CVHandler
def initialize
super(3)
end
def process(cv)
print(cv.name, 'match skills')
end
end
class CVApproveHandler < CVHandler
def initialize
super(4)
end
def process(cv)
print(cv.name, 'approve')
end
end
To handle CV we do these steps: upload -> scan skills -> matching skills with jobs -> approve. Thus, we implement those handlers. Each handler handles a status of CV. We next build chain of handler like single linked list.
class CVHandlerChain
def self.setup
uploading = CVUploadHandler.new
scanning = CVScanHandler.new
matching = CVMatchingHandler.new
approving = CVApproveHandler.new
uploading.next_handler = scanning
scanning.next_handler = matching
matching.next_handler = approving
uploading
end
end
uploading is the first node. The CV instance will be traversed over all nodes.
The main function code:
cv_process = CVHandlerChain.setup
cv = CV.new(1, 'Will Nguyen')
(1..5).each do |i|
cv.status = i
cv_process.handle(cv)
end
Output:
Will Nguyen CVUploadHandler => upload
Will Nguyen CVScanHandler => scan
Will Nguyen CVMatchingHandler => match skills
Will Nguyen CVApproveHandler => approve
That is an example. In real case we can implement the state pattern. Everytime cv changed state, it automatically execute handler chain.
class CV
attr_accessor :status, :name
def initialize(status, name)
@name = name
@cv_process = CVHandlerChain.setup
set_status(status)
end
def set_status(status)
@status = status
@cv_process.handle(self)
end
end
cv = CV.new(1, 'Will Nguyen')
(2..5).each { |status| cv.set_status(status) }
Same output.
Applying Dependency Injection, following Dependency Inversion in S.O.L.I.D. Why not?
def initialize(status, name, chain = CVHandlerChain)
@name = name
@cv_process = chain.setup
set_status(status)
end