본문 바로가기
Practical Python Design Patterns

Chain of Responsibility Pattern

by 자동매매 2023. 3. 28.

CHAPTER Chain of Responsibility Pattern

It wasn t me.

Shaggy, It Wasn t Me

Web apps are complex beasts. They need to interpret incoming requests and decide

what should be done with them. When you think about creating a framework to help you build web apps more quickly, you stand a very real chance of drowning in the complexity and options involved with such a project. Python is home to many a web framework since the language is popular among developers of cloud applications. In this chapter,

we are going to explore one component of web frameworks called the middleware layer.

The middleware sits in the middle, between the client making requests of the app and the actual application. It is a layer that both the requests from the client and the responses from the main application functions need to pass through. In the middleware, you can do things like log incoming requests for troubleshooting. You can also add some code to capture and save the response from the app that goes back to the user. Often, you will find code to check the user s access and whether they should be allowed to execute the request they are making on the system. You might want to translate the incoming request body into a data format that is easy to handle, or escape all input values to

protect the system from hackers injecting code into the app. You could also think of

the routing mechanism of the app as part of the middleware. The routing mechanism determines what code should be used to respond to requests directed at a specific endpoint.

143

' Wessel Badenhorst 2017

W. Badenhorst, Practical Python Design Patterns, https://doi.org/10.1007/978-1-4842-2680-3_10
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

Most web frameworks create a request object that is used throughout the system. A simple Request object might look like this:

class Request(object):

def __init__(self, headers, url, body, GET, POST):

self.headers

self.url

self.body

self.GET

self.POST

At the moment, you have no extra functionality associated with the request, but this limited object should be a good start for you to build up some intuition about the way middleware is used in handling client requests to a web app.

Web frameworks need to return some sort of response to the client. Let s define a basic Response object:

class Response(object):

def __init__(self, headers, status_code, body):

self.headers

self.status_code

self.body

If you look at the documentation of one of the more popular frameworks, like Django or Flask, you will see that the objects are way more involved that the simple ones just defined, but these will serve our purpose.

One of the things we would like our middleware to do is check for a token in the Request object s headers and then add the User object to the object. This will allow the main app to access the currently logged-in user to alter the interface and information displayed as needed.

Let s write a basic function that will grab the user token and load a User object from a database. The function will then return a User object that can be attached to the request object.

import User

def get_user_object(username, password):

return User.find_by_token(username, password)

144
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

You could easily create a User class that gets stored in a database. As an exercise, look at the SQLAlchemy project (https://www.sqlalchemy.org/) and create an interface to an SQLite database where you store basic user data. Make sure the User class has a class function that finds a user based on the user token field, which is unique.

Setting Up a WSGI Server

Let s make this a little more practical.

Since you already have pip running on your system, install uWSGI on your virtual environment:

pip install uwsgi

Windows users might receive an error message, AttributeError: module ’os’ has no attribute ’uname’, for this chapter, and in Chapter 12 you will need to install Cygwin.

Make sure to select the python3 interpreter, pip3, gcc-core, python3-devel (check

that you select your Windows version 32-bit/64-bit), libcrypt-devel, and wget from

the options during the Cygwin install process. You can get it at http://cygwin.com/ install.html. Cygwin is a Linux-like terminal for Windows.

You can navigate using default Linux commands, such as the pip3 install uwsgi command. You could also follow the virtualenv setup instructions from Chapter 1 to set

up a virtual environment in Cygwin (the activate command is located in YOURENVNAME/ Scripts).

If all of this seems like way too much effort, you would do well to install virtualbox and run Ubuntu on the box. Then, you can follow the Linux path completely, without

any changes in your day-to-day environment.

Let s test that the server works and can serve up websites on your machine. The following script returns a very simple status 200 success and Hello World message in

the body (from the uWSGI documentation at http://uwsgi-docs.readthedocs.io/en/ latest/WSGIquickstart.html).

hello_world_server.py

def application(env, start_response):

start_response(’200 OK’, [(’Content-Type’,’text/html’)]) return [b"Hello World"]

145
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

In your command line, with the virtual environment active, start the server, with hello_world_server.py set as the application to handle requests.

uwsgi --http :9090 --wsgi-file hello_world_server.py

If you point your browser to http://localhost:9090, you should see the message Hello World in the window.

Congratulations! You have your very own web server running; you could even deploy it on a server, point a URL at it, and let the world send requests to it. What we want to do next is see how the middleware fits into the flow from request to response by building it from the ground up.

Since the server is already up and running, let s add a function to check if the user token is set in the headers. We are going to follow a convention that is used by a number of bigger APIs you might find online.

Authentication Headers

The idea is that the client includes an Authorization header, where the header value

is set to the word Basic followed by a space, followed by a base64-encoded string. The string that gets encoded has the following form, USERNAME:PASSWORD, which will be used to identify and verify the user on the system.

Before we continue building up the functionality, it is important to note that by including the Authentication header whenever you make a call to the app, and having

the app check for the Authorization header, you remove the need for the app to keep

track of which user is making the calls, and instead you get to attach the user to the

request when that request happens.

The easiest way to test URL endpoints is to install a tool like postman on your system. It allows you to request URLs from the app and interpret the results. The free postman

app can be downloaded here: https://www.getpostman.com/.

Once the postman install concludes, open the app.

First, enter the same URL you entered in your browser into the postman address bar and click Send or press Enter. You should see Hello World in the box at the bottom of the screen. This tells you that postman is working and your app is still running.

146
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

Right below the address bar, you will see a box with a couple of tabs attached to it. One of these tabs is Authorization. Click it and select Basic Auth from the dropdown box. Now username and password fields are added to the interface. Postman allows you to enter the username and password in these boxes, and it will then encode the string for you.

To see what the Request object you receive from uWSGI looks like, you need to change the script you used to return the Hello World message to the browser.

hello_world_request_display.py import pprint

pp = pprint.PrettyPrinter(indent=4)

def application(env, start_response):

pp.pprint(env)

start_response(’200 OK’, [(’Content-Type’,’text/html’)]) return [b"Hello World "]

The pprint module included in the Python standard library prints data types like

dictionaries and lists and is an easy-to-read format. It is a valuable tool when you are

debugging complex datatypes, like the request received from a web server.

When you restart your uWSGI server and send with postman the request including

the Authorization headers, you will see a dictionary printed in the console. One of the

keys in the dictionary will be HTTP_AUTHORIZATION, which is the field containing the

word Basic followed by a space, followed by a base64-encoded string.

To retrieve the User object from the database, this string needs to be decoded and

then split into the username and password respectively. We can do all of this by updating

the server application. We will begin by obtaining the value of the HTTP_AUTHORIZATION header. Then, we will split that value on a space, giving us the word Basic and the base64-encoded string. We will then proceed to decode the string and split on the :,

which will result in an array with the username at index 0 and the password for the user

147
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

at index 1. We will then send this to a simple function that returns a User object, which we just construct inline for the sake of this example, but you are welcome to plug in your function that gets a user from your local SQLite database.

user_aware_server.py import base64

class User(object):

def __init__(self, username, password, name, email):

self.username = username

self.password = password

self.name = name

self.email = email

@classmethod

def get_verified_user(cls, username, password):

return User(

username,

password,

username, "{}@demo.com".format(username)

)

def application(env, start_response):

authorization_header = env["HTTP_AUTHORIZATION"] header_array = authorization_header.split()

encoded_string = header_array[1]

decoded_string = base64.b64decode(encoded_string).decode(’utf-8’) username, password = decoded_string.split(":")

user = User.get_verified_user(username, password)

start_response(’200 OK’, [(’Content-Type’, ’text/html’)]) response = "Hello {}!".format(user.name)

return [response.encode(’utf-8’)]

148
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

In our example, we created a user based on the request. This is clearly not correct, but the fact that we separated it into its own class allows us to take the code and change the get_verified_user method to get the user based on username. We then verify

that the password for that user is correct. Then, you might want to add some code to handle the instance where the user does not exist in the database.

At this time, we already see a lot of code relevant only to the process of checking user credentials in the main server application. This feels wrong. Let s emphasize just how wrong it is by adding the most basic routing code possible. Our router will look for a header, which tells us which endpoint was requested by the client. Based on this information, we will print a hello message for all cases, except if the path contains the substring goodbye, in which case the server will return a goodbye message.

When we print out the request contents, we see that the path information is captured in the PATH_INFO header. The header starts with a leading / followed by the rest of the path.

You can split the path on / and then check if the path contains the word goodbye.

import base64

from pprint import PrettyPrinter pp = PrettyPrinter(indent=4)

class User(object):

def __init__(self, username, password, name, email):

self.username = username

self.password = password

self.name = name

self.email = email

@classmethod

def get_verified_user(cls, username, password):

return User(

username,

password,

username, "{}@demo.com".format(username)

)

149
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

def application(env, start_response):

authorization_header = env["HTTP_AUTHORIZATION"] header_array = authorization_header.split()

encoded_string = header_array[1]

decoded_string = base64.b64decode(encoded_string).decode(’utf-8’) username, password = decoded_string.split(":")

user = User.get_verified_user(username, password) start_response(’200 OK’, [(’Content-Type’, ’text/html’)])

path = env["PATH_INFO"].split("/")

if "goodbye" in path:

response = "Goodbye {}!".format(user.name) else:

response = "Hello {}!".format(user.name) return [response.encode(’utf-8’)]

I m sure your code sense is making a lot of noise right now. The application function is clearly doing three different things right now. It starts out getting the User object. Then, it decides what endpoint the client is requesting and composes the relevant message based on that path. Lastly, it returns the encoded message to the client who made the request.

Whenever more functionality is required between the initial request from the user and the actual response, the application function becomes more complex. In short, your code is bound to become a mess if you continue down this path.

We want each function to ideally do one and only one thing.

The Chain of Responsibility Pattern

The idea that every piece of code does one thing and only one thing is called the single responsibility principle, which is another handy guideline when you are working on writing better code.

150
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

If we wanted to adhere to the single responsibility principle in the preceding example, we would need a piece of code to handle the user retrieval and another piece of code to validate that the user matches the password. Yet another piece of code would generate a message based on the path of the request, and, finally, some piece of code would return the response to the browser.

Before we attempt to write the code for the server, we will build up some intuition about the solution using a sample application.

In our sample application, we have four functions, each of which simply prints out a message.

def function_1():

print("function_1")

def function_2():

print("function_2")

def function_3():

print("function_3")

def function_4():

print("function_4")

def main_function(): function_1()

function_2()

function_3()

function_4()

if __name__ == "__main__": main_function()

From our discussion in the beginning of this section, we know that it is not ideal to have the main_function call each of the functions in order, since in a real-world scenario this leads to the messy code we encountered in the previous section.

151
CHAPTER 11 CHAIN OF RESPONSIBILITY PATTERN

What we want is to create some sort of way for us to make a single call and then have the functions called dynamically.

class CatchAll(object):

def __init__(self):

self.next_to_execute = None

def execute(self):

print("end reached.")

class Function1Class(object):

def __init__(self):

self.next_to_execute = CatchAll()

def execute(self):

print("function_1") self.next_to_execute.execute()

class Function2Class(object):

def __init__(self):

self.next_to_execute = CatchAll()

def execute(self):

print("function_2") self.next_to_execute.execute()

class Function3Class(object):

def __init__(self):

self.next_to_execute = CatchAll()

def execute(self):

print("function_3") self.next_to_execute.execute()

class Function4Class(object):

def __init__(self):

self.next_to_execute = CatchAll()

def execute(self):

print("function_4") self.next_to_execute.execute()

152
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

def main_function(head): head.execute()

if __name__ == "__main__":

hd = Function1Class()

current = hd

current.next_to_execute = Function2Class()

current = current.next_to_execute current.next_to_execute = Function3Class()

current = current.next_to_execute current.next_to_execute = Function4Class()

main_function(hd)

Even though the amount of code written is significantly more than in the first instance, you should begin to see how cleanly this new program separates the execution of each function from the others.

To illustrate this further, we now extend the main execute function to include a request string. The string is a series of digits, and if the digit from the function name is included in the digit string, the function prints its name together with the request string before it removes all instances of the digit in question from the request string and passes the request on to the rest of the code for processing.

In our original context, we could solve this new requirement as follows:

def function_1(in_string):

print(in_string)

return "".join([x for x in in_string if x != ’1’]) def function_2(in_string):

print(in_string)

return "".join([x for x in in_string if x != ’2’]) def function_3(in_string):

print(in_string)

return "".join([x for x in in_string if x != ’3’])

153
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

def function_4(in_string):

print(in_string)

return "".join([x for x in in_string if x != ’4’])

def main_function(input_string):

if ’1’ in input_string:

input_string = function_1(input_string) if ’2’ in input_string:

input_string = function_2(input_string) if ’3’ in input_string:

input_string = function_3(input_string) if ’4’ in input_string:

input_string = function_4(input_string)

print(input_string)

if __name__ == "__main__":

main_function("1221345439")

Yes, you are right all the functions do exactly the same thing, but this is just because of the way the sample problem is set up. If you were to refer to the original problem, you would see that this is not always the case.

What should also feel off is the series of if statements that will only grow in length as more functions are added to the execution queue.

Finally, you may have noted how closely coupled the main_function is to each of the functions being executed.

Implementing Chain of Responsibility in Our Project

Now, let s solve the same problem using the idea we began to build earlier.

class CatchAll(object):

def __init__(self):

self.next_to_execute = None

def execute(self, request):

print(request)

154
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

class Function1Class(object):

def __init__(self):

self.next_to_execute = CatchAll()

def execute(self, request):

print(request)

request = "".join([x for x in request if x != ’1’]) self.next_to_execute.execute(request)

class Function2Class(object):

def __init__(self):

self.next_to_execute = CatchAll()

def execute(self, request):

print(request)

request = "".join([x for x in request if x != ’2’]) self.next_to_execute.execute(request)

class Function3Class(object):

def __init__(self):

self.next_to_execute = CatchAll()

def execute(self, request):

print(request)

request = "".join([x for x in request if x != ’3’]) self.next_to_execute.execute(request)

class Function4Class(object):

def __init__(self):

self.next_to_execute = CatchAll()

def execute(self, request):

print(request)

request = "".join([x for x in request if x != ’4’]) self.next_to_execute.execute(request)

155
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

def main_function(head, request): head.execute(request)

if __name__ == "__main__":

hd = Function1Class()

current = hd

current.next_to_execute = Function2Class()

current = current.next_to_execute current.next_to_execute = Function3Class()

current = current.next_to_execute current.next_to_execute = Function4Class()

main_function(hd, "1221345439")

Already it is clear that we have a much cleaner solution at hand. We did not have to make any changes to the way each class is connected to the next, and apart from passing on the request, we did not require any changes to be made to the individual execute functions or the main function in order to accommodate the way each class executes the request. We have clearly separated each function into its own unit of code that can be plugged into, or removed from, the chain of classes dealing with the request.

Each handler cares only about its own execution and ignores what happens when another handler executes because of the query. The handler decides in the moment

if it should do anything as a result of the request it receives, which leads to increased flexibility in determining which handlers should execute as a result of a query. The best part of this implementation is that handlers can be added and removed at runtime, and when the order is unimportant in terms of the execution queue, you could even shuffle the order of the handlers. This only goes to show, once again, that loosely coupled code is more flexible than tightly coupled code.

156
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

To further illustrate this flexibility, we extend the previous program so each class will only do something if it sees that the digit associated with the class name is in the request string.

class CatchAll(object):

def __init__(self):

self.next_to_execute = None

def execute(self, request):

print(request)

class Function1Class(object):

def __init__(self):

self.next_to_execute = CatchAll()

def execute(self, request):

if ’1’ in request:

print("Executing Type Function1Class with request [{}]". format(request))

request = "".join([x for x in request if x != ’1’])

self.next_to_execute.execute(request)

class Function2Class(object):

def __init__(self):

self.next_to_execute = CatchAll()

def execute(self, request):

if ’2’ in request:

print("Executing Type Function2Class with request [{}]". format(request))

request = "".join([x for x in request if x != ’2’])

self.next_to_execute.execute(request)

class Function3Class(object):

def __init__(self):

self.next_to_execute = CatchAll()

157
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

def execute(self, request):

if ’3’ in request:

print("Executing Type Function3Class with request [{}]". format(request))

request = "".join([x for x in request if x != ’3’])

self.next_to_execute.execute(request)

class Function4Class(object):

def __init__(self):

self.next_to_execute = CatchAll()

def execute(self, request):

if ’4’ in request:

print("Executing Type Function4Class with request [{}]". format(request))

request = "".join([x for x in request if x != ’4’])

self.next_to_execute.execute(request)

def main_function(head, request): head.execute(request)

if __name__ == "__main__":

hd = Function1Class()

current = hd

current.next_to_execute = Function2Class()

current = current.next_to_execute current.next_to_execute = Function3Class()

current = current.next_to_execute current.next_to_execute = Function4Class()

main_function(hd, "12214549")

158
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

Note that this time we do not have any 3 digits in the request, and as a result the code for the instance of Function3Class is not executed before the request is passed on to the instance of Function4Class. Also, note that we had no change in the code for the setup of the execution chain or in main_function.

This chain of handlers is the essence of the Chain of Responsibility pattern.

A More Pythonic Implementation

In the classic implementation of this pattern, you would define some sort of generic handler type that would define the concept of the next_to_execute object as well as

the execute function. As we have seen before, the duck-typing system in Python frees

us from having to add the extra overhead. As long as we define the relevant parts for each handler we want to use in the chain, we do not need to inherit from some shared base. This not only saves a lot of time otherwise spent writing boilerplate code, but also enables you, the developer, to easily evolve these types of ideas as your application or system calls for it.

In more constrained languages, you do not have the luxury of evolving the design

on the fly. Changing direction like this often requires a large redesign, with a lot of code requiring a rewrite. To avoid the pain of redesigning the whole system or rebuilding large parts of the application, engineers and developers try to cater to every eventuality they can dream up. This approach is flawed in two critical ways. First, human knowledge of the future is imperfect, so even if you were to design the perfect system for today (a near impossibility), it would be impossible to account for every way in which clients might use your system, or for where the market might drive your application. The second is that, for the most part, You Ain t Gonna Need It (YAGNI), or, stated differently, most of the potential futures that get covered in the design of such a system will never be needed, and as such it is time and effort that has gone to waste. Once again, Python provides the capability to start with a simple solution to a simple problem and then grow the solution as the use cases change and evolve. Python moves with you, changing and growing

as you do. Much like the game of Go, Python is easy to pick up, but you can devote a lifetime to mastering its many possibilities.

159
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

Here is a simple implementation of the Chain of Responsibility pattern, with all of the extras stripped away. All that is left is the general structure that we will be implementing on our web app.

class EndHandler(object): def __init__(self):

pass

def handle_request(self, request):

pass

class Handler1(object):

def __init__(self):

self.next_handler = EndHandler()

def handle_request(self, request):

self.next_handler.handle_request(request)

def main(request):

concrete_handler = Handler1()

concrete_handler.handle_request(request)

if __name__ == "__main__":

  • from the command line we can define some request main(request)

We will now use this very minimal skeleton to significantly improve our web

app from the beginning of this chapter. The new solution will adhere to the single responsibility principle much more closely. You will also note that you could add many more functions and extensions to the process.

import base64

class User(object):

def __init__(self, username, password, name, email):

self.username = username

self.password = password

160
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

self.name = name self.email = email

@classmethod

def get_verified_user(cls, username, password):

return User(

username,

password,

username, "{}@demo.com".format(username)

)

class EndHandler(object): def __init__(self):

pass

def handle_request(self, request, response=None):

return response.encode(’utf-8’)

class AuthorizationHandler(object):

def __init__(self):

self.next_handler = EndHandler()

def handle_request(self, request, response=None):

authorization_header = request["HTTP_AUTHORIZATION"] header_array = authorization_header.split()

encoded_string = header_array[1]

decoded_string = base64.b64decode(encoded_string).decode(’utf-8’) username, password = decoded_string.split(":")

request[’username’] = username

request[’password’] = password

return self.next_handler.handle_request(request, response)

class UserHandler(object):

def __init__(self):

self.next_handler = EndHandler()

161
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

def handle_request(self, request, response=None):

user = User.get_verified_user(request[’username’], request[’password’])

request[’user’] = user

return self.next_handler.handle_request(request, response)

class PathHandler(object):

def __init__(self):

self.next_handler = EndHandler()

def handle_request(self, request, response=None):

path = request["PATH_INFO"].split("/")

if "goodbye" in path:

response = "Goodbye {}!".format(request[’user’].name) else:

response = "Hello {}!".format(request[’user’].name) return self.next_handler.handle_request(request, response)

def application(env, start_response):

head = AuthorizationHandler()

current = head

current.next_handler = UserHandler()

current = current.next_handler current.next_handler = PathHandler()

start_response(’200 OK’, [(’Content-Type’, ’text/html’)]) return [head.handle_request(env)]

Another alternative implementation of the Chain of Responsibility pattern is to instantiate a main object to dispatch handlers from a list passed into the dispatcher on instantiation.

162
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

class Dispatcher(object):

def __init__(self, handlers=[]):

self.handlers = handlers

def handle_request(self, request): for handler in self.handlers: request = handler(request)

return request

The Dispatcher class takes advantage of the fact that everything in Python is an object, including functions. Since functions are first-class citizens (which means they can be passed to a function and be returned from a function) we can instantiate the dispatcher and pass in a list of functions, which can be saved like any other list. When the time comes to execute the functions, we iterate over the list and execute each function in turn.

Implementing the sample code we have been using in this section using the Dispatcher looks like this:

class Dispatcher(object):

def __init__(self, handlers=[]):

self.handlers = handlers

def handle_request(self, request): for handle in self.handlers:

request = handle(request)

return request

def function_1(in_string):

print(in_string)

return "".join([x for x in in_string if x != ’1’]) def function_2(in_string):

print(in_string)

return "".join([x for x in in_string if x != ’2’])

163
CHAPTER 11 CHAIN OF RESPONSIBILITY PATTERN

def function_3(in_string):

print(in_string)

return "".join([x for x in in_string if x != ’3’]) def function_4(in_string):

print(in_string)

return "".join([x for x in in_string if x != ’4’])

def main(request):

dispatcher = Dispatcher([

function_1, function_2,

function_3, function_4

]) dispatcher.handle_request(request)

if __name__ == "__main__":

main("1221345439")

As an exercise, I suggest you use this structure to implement the process for the web app.

Before we wrap up this chapter, I want you to think about the discussion on middleware that we started with. Can you see how you would use the Chain of Responsibility pattern to implement a middleware for your web app by adding handlers to the queue?

Parting Shots

As we have seen throughout this chapter, the request is the central object around which the pattern is built, and as such care should be taken when constructing and altering the request object as it gets passed from one handler to the next.

Since the Chain of Responsibility pattern affords us the opportunity to shuffle and alter the handlers used at runtime, we can apply some performance-enhancing ideas

to the chain. We could move a handler to the front of the chain every time it is used, resulting in more regularly used handlers taking the early positions in the chain followed

164
CHAPTER CHAIN OF RESPONSIBILITY PATTERN

by the less frequently used handlers (use this only when the order of execution of the handlers does not matter). When the order of the handlers has some sort of meaning, you could get some improved performance using ideas from the previous chapter and caching the results of certain handled requests.

We never want to completely ignore a request, so you must make sure to have some sort of end handler serving as a catch-all for the chain to, at the very least, alert you that the request was not handled by any of the handlers in your chain.

In the examples from this chapter, we had the chain execute until all the handlers had a look at the request. This does not have to be the case; you can break the chain at any point and return a result.

The Chain of Responsibility pattern can serve to encapsulate a processing of elements into a pipeline (like a data pipeline).

Finally, a good rule of thumb is to use the Chain of Responsibility pattern wherever you have more than one potential handler for a request, and thus do not know beforehand which handler or handlers would best handle the requests you will receive.

Exercises

Look at SQLAlchemy and create an interface to an SQLite database

where you store basic user data.

Expand the User class function that retrieves the user so it uses the

SQLite database, as in the previous exercise.

Add the logger you built in chapter 2, on the singleton pattern, to the

chain of responsibility of your web application so you can log the incoming request and the responses sent back to the client.

Implement your web app from this chapter using the Dispatcher

implementation of the Chain of Responsibility.

165

'Practical Python Design Patterns' 카테고리의 다른 글

Interpreter Pattern  (0) 2023.03.28
Command Pattern  (0) 2023.03.28
Proxy Pattern  (0) 2023.03.28
Facade Pattern  (0) 2023.03.28
Decorator Pattern  (0) 2023.03.28

댓글