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 '<html>'
print ' <head>'
print ' <title>%s</title>' % title
print ' </head>'
print ' <body>'
for line in text:
print ' <p>%s</p>' % line
print ' </body>'
print '</html>'
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.
2 Answers 2
In my opinion, this is abuse of the Chain-of-responsibility Pattern. It is completely surprising that pdf_handler.handle(report)
would generate a text report instead.
The Wikipedia example for Chain-of-responsibility is a logger. As the message passes through the chain, each logger may choose to do something with it (write to standard output, send e-mail, etc.) depending on the verbosity level.
Another example of Chain-of-responsibility is the filter mechanism in Apache HTTPD. Each input or output filter in the chain can alter the request or response.
Your use case is different: you only want to generate one kind of report. For that, use a subtly different design: the Strategy Pattern. Basically, just do the simplest thing that could work:
- Each kind of handler defines a
handle(self, request)
method. - If you want a text report, then call
TextReport().handle(request)
.
-
\$\begingroup\$ No, its not an abuse. See this description \$\endgroup\$vivek– vivek2014年05月25日 07:33:01 +00:00Commented May 25, 2014 at 7:33
-
1\$\begingroup\$ Yes, it is abuse, as I just explained. As your article states, Chain of Responsibility "[avoids] coupling the sender of a request to its receiver by giving more than one object a chance to handle the request." But you have no reason to let multiple objects handle the request. I've given examples of where a chain would be appropriate; your problem does not fit that pattern. \$\endgroup\$200_success– 200_success2014年05月25日 07:40:34 +00:00Commented May 25, 2014 at 7:40
-
\$\begingroup\$ The request should be handled by any on object, read it again. \$\endgroup\$vivek– vivek2014年05月26日 17:36:12 +00:00Commented May 26, 2014 at 17:36
-
1\$\begingroup\$ +1 I totally agree with your answer, but rereading both articles linked here I see the examples use the same design where each link has a reference to the next link in the chain. By moving the chain creation into a separate method, you don't realize that the first link in the chain is being referenced by clients directly. Clients think they have a generic
ReportHandler
when in reality they hold thePDFHandler
. \$\endgroup\$David Harkness– David Harkness2014年05月26日 19:22:36 +00:00Commented May 26, 2014 at 19:22
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 '<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)
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.
-
\$\begingroup\$ Hi, your example is similar to the strategy pattern. In my example I just tried to show the use case of the command pattern, maybe my use case is not so clear. I think the main problem here is I am calling directly the concrete report object maybe if I put them is some container and then calling a method on the container may solve the issue. \$\endgroup\$vivek– vivek2014年05月27日 02:14:08 +00:00Commented May 27, 2014 at 2:14
-
\$\begingroup\$ maybe this link provides some more insight oodesign.com/chain-of-responsibility-pattern.html \$\endgroup\$vivek– vivek2014年05月27日 02:16:40 +00:00Commented May 27, 2014 at 2:16
-
\$\begingroup\$ @vivekpoddar Your choice of use case is probably the only real issue. See my small update. \$\endgroup\$David Harkness– David Harkness2014年05月27日 02:42:10 +00:00Commented May 27, 2014 at 2:42
-
\$\begingroup\$ Although the use case regarding request handling differs here. I myself will not use this pattern in the above case, it was because I was not finding some useful use case I implemented in the above scenario. \$\endgroup\$vivek– vivek2014年05月27日 05:44:43 +00:00Commented May 27, 2014 at 5:44