Posted almost 5 years ago. Visible to the public.

Chain of Responsibility Pattern

The chain of responsibility is most useful when related objects are all trying to handle the same request. Let's say we have a series of image objects (Jpeg, Gif, PNG) that are all trying to import an image. Each will extract and store meta data from the image and maybe create a standard-use thumbnail, but how depends on which kind of image. And so we have if statements to use the Jpeg image to import jpeg images, the Gif image to import Gif images, and so on. The conditionals properly send a file ending with .jpg to JpegImage. A file ending with .gif goes through GifImage. The unknown image doesn’t get picked up by anything so it gets handled byUnknownImage (likely storing the raw date without meta info or thumbnail). But the .jpeg image and the JPEG image with the wrong extension are both mishandled.

Copy
if extension == 'gif' GifImage.new(filename).import elsif (extension == 'jpg') JpegImage.new(filename).import elsif (extension == 'png') PngImage.new(filename).import else UnknownImage.new(filename).import end

The nifty idea of the chain of responsibility comes from the recognition that the different handlers (JpegImage, GifImage, PngImage, etc.) are a… chain. A chain of request handlers. If one link in the chain cannot handle the request — if one image handler cannot handle the import request — then the next handler in the chain gets a chance. Phrased like that, we can push the requests down into the handler method — import(). If the current handler cannot handle the request, then ask the next handler to do so. If it cannot, then it asks the next handler to try. And so on until the request is handled.

Copy
%w(asdf.gif bar.jpeg baz.png foo.jpg jpeg.aaa 1.txt).each do |filename| ImageImporter.new(filename).import ImageImporter.new(filename).import_only_when_jpeg end ### Pattern name: Handler ### class Image def initialize(file_name) @file_name = file_name end ### Pattern name: Handle Request ### def import @next_importer.import end def import_only_when_jpeg @next_importer.import_only_when_jpeg end def extension parts = @file_name.split('.') return nil if parts.length != 2 parts[1] end def read_initial_bytes FileMock.open(@file_name, 'rb') do |file| file.read end end end ### Pattern name: Concrete Handler ### class ImageImporter < Image def initialize(file_name) super(file_name) @next_importer = JpegImage.new(file_name) end end class JpegImage < Image def initialize(file_name) super(file_name) @next_importer = GifImage.new(file_name) end def import return super unless can_import? make_import end def import_only_when_jpeg return super unless can_import? make_import end private def make_import puts "Importing \"#{@file_name}\" as a JPEG image." end def can_import? extension == 'jpg' || extension == 'jpeg' || read_initial_bytes.include?('JPEG') end end class GifImage < Image def initialize(file_name) super(file_name) @next_importer = PngImage.new(file_name) end def import return super unless can_import? puts "Importing \"#{@file_name}\" as a GIF image." end private def can_import? extension == 'gif' end end class PngImage < Image def initialize(file_name) super(file_name) @next_importer = UnknownImage.new(file_name) end def import return super unless can_import? puts "Importing \"#{@file_name}\" as a PNG image." end private def can_import? extension == 'png' end end class UnknownImage < Image def initialize(file_name) super(file_name) end def import puts "Importing \"#{@file_name}\" as an unknown image." end def import_only_when_jpeg puts "Can't import \"#{@file_name}\" because it ain't JPEG" end end ### Helper code, to mimic ruby standard File object ### class FileMock MOCKS_HASH = { 'asdf.gif' => 'GIF', 'bar.jpeg' => 'JPEG', 'baz.png' => 'PNG', 'foo.jpg' => 'JPEG', 'jpeg.aaa' => 'JPEG' } def self.open(path, *args, &block) yield FileMock.new(path) end def initialize(file_path) @file_path = file_path end def read return MOCKS_HASH[@file_path] if MOCKS_HASH.has_key?(@file_path) 'HUH!?' end end

Owner of this card:

Avatar
Alexander M
Last edit:
almost 5 years ago
by Alexander M
Tags:
Software-Architecture
Posted by Alexander M to Ruby and RoR knowledge base
This website uses short-lived cookies to improve usability.
Accept or learn more