HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Chain of responsibility pattern

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
patternresponsibilitychain

Problem

class ReportFormat(object):
    PDF = 0
    TEXT = 1

class Report(object):
    """Strategy context."""

    def __init__(self, format_):
        self.title = 'Monthly report'
        self.text = ['Things are going', 'really, really well.']
        self.format_ = format_

class Handler(object):

    def __init__(self):
        self.nextHandler = None

    def handle(self, request):
        self.nextHandler.handle(request)

class PDFHandler(Handler):

    def handle(self, request):
        if request.format_ == ReportFormat.PDF:
            self.output_report(request.title, request.text)
        else:
            super(PDFHandler, self).handle(request)

    def output_report(self, title, text):
        print ''
        print '  '
        print '    %s' % title
        print '  '
        print '  '
        for line in text:
            print '    %s' % line
        print '  '
        print ''

class TextHandler(Handler):

    def handle(self, request):
        if request.format_ == ReportFormat.TEXT:
            self.output_report(request.title, request.text)
        else:
            super(TextHandler, self).handle(request)

    def output_report(self, title, text):
        print 5*'*' + title + 5*'*'
        for line in text:
            print line

class ErrorHandler(Handler):

    def handle(self, request):
        print "Invalid request"

if __name__ == '__main__':
    report = Report(ReportFormat.TEXT)
    pdf_handler = PDFHandler()
    text_handler = TextHandler()

    pdf_handler.nextHandler = text_handler
    text_handler.nextHandler = ErrorHandler()

    pdf_handler.handle(report)


O/P:

Monthly report
Things are going
really, really well.

Solution

I would not use Chain of Responsibility to solve this problem. For every request, a single handler is chosen based on the same property: it's output format. For that you can use a simple dispatch table: a dict mapping each output format to its handler. There's no point asking each handler if it can handle the request.

Update: As usual, the Portland Pattern Repository has some good historical discussion of this pattern, specifically when not to use it:


Do not use Chain of Responsibility when each request is only handled by one handler, or, when the client object knows which service object should handle the request.

Note: Report and ReportFormat are unchanged from the OP and omitted for brevity.

class ReportDispatcher(object):
    def __init__(self):
        self.reports = {}

    def add(self, report):
        self.reports[report.format] = report;

    def handle(self, request):
        report = self.reports[request.format_]
        if report:
            report.handle(request)
        else
            print "Invalid request"

class Handler(object):

    def __init__(self, format):
        self.format = format

    def handle(self, request):
        print "subclass responsibility"

class PDFHandler(Handler):

    def __init__(self):
        super(PDFHandler, self).__init__(ReportFormat.PDF)

    def handle(self, request):
        print ''
        print '  '
        print '    %s' % request.title
        print '  '
        print '  '
        for line in request.text:
            print '    %s' % line
        print '  '
        print ''

class TextHandler(Handler):

    def __init__(self):
        super(TextHandler, self).__init__(ReportFormat.TEXT)

    def handle(self, request):
        print 5*'*' + request.title + 5*'*'
        for line in request.text:
            print line

if __name__ == '__main__':
    reports = ReportDispatcher()
    reports.add(PDFHandler())
    reports.add(TextHandler())

    report = Report(ReportFormat.TEXT)
    reports.handle(report)


As you can see from the main method, sending a report request is completely decoupled from the various report handlers. Instead, the request is sent to the dispatcher which knows about the handlers.

Code Snippets

class ReportDispatcher(object):
    def __init__(self):
        self.reports = {}

    def add(self, report):
        self.reports[report.format] = report;

    def handle(self, request):
        report = self.reports[request.format_]
        if report:
            report.handle(request)
        else
            print "Invalid request"

class Handler(object):

    def __init__(self, format):
        self.format = format

    def handle(self, request):
        print "subclass responsibility"

class PDFHandler(Handler):

    def __init__(self):
        super(PDFHandler, self).__init__(ReportFormat.PDF)

    def handle(self, request):
        print '<html>'
        print '  <head>'
        print '    <title>%s</title>' % request.title
        print '  </head>'
        print '  <body>'
        for line in request.text:
            print '    <p>%s</p>' % line
        print '  </body>'
        print '</html>'

class TextHandler(Handler):

    def __init__(self):
        super(TextHandler, self).__init__(ReportFormat.TEXT)

    def handle(self, request):
        print 5*'*' + request.title + 5*'*'
        for line in request.text:
            print line

if __name__ == '__main__':
    reports = ReportDispatcher()
    reports.add(PDFHandler())
    reports.add(TextHandler())

    report = Report(ReportFormat.TEXT)
    reports.handle(report)

Context

StackExchange Code Review Q#51429, answer score: 4

Revisions (0)

No revisions yet.