Skip to content

Commit 03e8e32

Browse files
committed
Refactor chain pattern
1 parent 2e8ae94 commit 03e8e32

1 file changed

Lines changed: 53 additions & 150 deletions

File tree

behavioral/chain.py

Lines changed: 53 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -3,192 +3,95 @@
33

44
"""
55
*What is this pattern about?
6-
This pattern aims to decouple the senders of a request from its
7-
receivers. It does this by allowing a request to move through chained
8-
objects until it is handled by an appropriate receiver.
96
10-
This is useful as it reduces the number of connections between objects,
11-
since the sender does not need explicit knowledge of the handler, and
12-
the receiver won't need to refer to all potential receivers, but keeps
13-
a reference to a single successor.
7+
The Chain of responsibility is an object oriented version of the
8+
`if ... elif ... elif ... else ...` idiom, with the
9+
benefit that the condition–action blocks can be dynamically rearranged
10+
and reconfigured at runtime.
11+
12+
This pattern aims to decouple the senders of a request from its
13+
receivers by allowing request to move through chained
14+
receivers until it is handled.
1415
15-
*References:
16-
http://www.dabeaz.com/coroutines/
16+
Request receiver in simple form keeps a reference to a single successor.
17+
As a variation some receivers may be capable of sending requests out
18+
in several directions, forming a `tree of responsibility`.
1719
1820
*TL;DR80
19-
Allow a request to pass down a chain of objects until an object handles
20-
the request.
21+
Allow a request to pass down a chain of receivers until it is handled.
2122
"""
2223

23-
from contextlib import contextmanager
24-
import os
25-
import sys
26-
import time
2724
import abc
2825

2926

3027
class Handler(object):
3128
__metaclass__ = abc.ABCMeta
3229

3330
def __init__(self, successor=None):
34-
self._successor = successor
31+
self.successor = successor
3532

3633
def handle(self, request):
37-
res = self._handle(request)
38-
if not res:
39-
self._successor.handle(request)
34+
"""
35+
Handle request and stop.
36+
If can't - call next handler in chain.
37+
38+
As an alternative you might even in case of success
39+
call the next handler.
40+
"""
41+
res = self.compare(request)
42+
if not res and self.successor:
43+
self.successor.handle(request)
4044

4145
@abc.abstractmethod
42-
def _handle(self, request):
43-
raise NotImplementedError('Must provide implementation in subclass.')
46+
def compare(self, request):
47+
"""Compare passed value to predefined interval"""
48+
49+
50+
class ConcreteHandler0(Handler):
51+
def compare(self, request):
52+
if 0 <= request < 10:
53+
print('request {} handled in handler 0'.format(request))
54+
return True
4455

4556

4657
class ConcreteHandler1(Handler):
47-
def _handle(self, request):
48-
if 0 < request <= 10:
58+
def compare(self, request):
59+
if 10 <= request < 20:
4960
print('request {} handled in handler 1'.format(request))
5061
return True
5162

5263

5364
class ConcreteHandler2(Handler):
54-
def _handle(self, request):
55-
if 10 < request <= 20:
65+
def compare(self, request):
66+
if 20 <= request < 30:
5667
print('request {} handled in handler 2'.format(request))
5768
return True
5869

5970

60-
class ConcreteHandler3(Handler):
61-
def _handle(self, request):
62-
if 20 < request <= 30:
63-
print('request {} handled in handler 3'.format(request))
64-
return True
65-
66-
6771
class DefaultHandler(Handler):
68-
def _handle(self, request):
72+
def compare(self, request):
6973
print('end of chain, no handler for {}'.format(request))
70-
return True
71-
72-
73-
class Client(object):
74-
def __init__(self):
75-
self.handler = ConcreteHandler1(ConcreteHandler3(ConcreteHandler2(DefaultHandler())))
76-
77-
def delegate(self, requests):
78-
for request in requests:
79-
self.handler.handle(request)
80-
81-
82-
def coroutine(func):
83-
def start(*args, **kwargs):
84-
cr = func(*args, **kwargs)
85-
next(cr)
86-
return cr
87-
88-
return start
89-
90-
91-
@coroutine
92-
def coroutine1(target):
93-
while True:
94-
request = yield
95-
if 0 < request <= 10:
96-
print('request {} handled in coroutine 1'.format(request))
97-
else:
98-
target.send(request)
99-
100-
101-
@coroutine
102-
def coroutine2(target):
103-
while True:
104-
request = yield
105-
if 10 < request <= 20:
106-
print('request {} handled in coroutine 2'.format(request))
107-
else:
108-
target.send(request)
109-
110-
111-
@coroutine
112-
def coroutine3(target):
113-
while True:
114-
request = yield
115-
if 20 < request <= 30:
116-
print('request {} handled in coroutine 3'.format(request))
117-
else:
118-
target.send(request)
119-
120-
121-
@coroutine
122-
def default_coroutine():
123-
while True:
124-
request = yield
125-
print('end of chain, no coroutine for {}'.format(request))
126-
127-
128-
class ClientCoroutine:
129-
def __init__(self):
130-
self.target = coroutine1(coroutine3(coroutine2(default_coroutine())))
131-
132-
def delegate(self, requests):
133-
for request in requests:
134-
self.target.send(request)
135-
136-
137-
def timeit(func):
138-
def count(*args, **kwargs):
139-
start = time.time()
140-
res = func(*args, **kwargs)
141-
count._time = time.time() - start
142-
return res
143-
144-
return count
145-
146-
147-
@contextmanager
148-
def suppress_stdout():
149-
try:
150-
stdout, sys.stdout = sys.stdout, open(os.devnull, 'w')
151-
yield
152-
finally:
153-
sys.stdout = stdout
74+
return False
15475

15576

15677
if __name__ == "__main__":
157-
client1 = Client()
158-
client2 = ClientCoroutine()
159-
requests = [2, 5, 14, 22, 18, 3, 35, 27, 20]
78+
h0 = ConcreteHandler0()
79+
h1 = ConcreteHandler1()
80+
h2 = ConcreteHandler2(DefaultHandler())
81+
h0.successor = h1
82+
h1.successor = h2
16083

161-
client1.delegate(requests)
162-
print('-' * 30)
163-
client2.delegate(requests)
164-
165-
requests *= 10000
166-
client1_delegate = timeit(client1.delegate)
167-
client2_delegate = timeit(client2.delegate)
168-
with suppress_stdout():
169-
client1_delegate(requests)
170-
client2_delegate(requests)
171-
# lets check which is faster
172-
print(client1_delegate._time, client2_delegate._time)
84+
requests = [2, 5, 14, 22, 18, 3, 35, 27, 20]
85+
for request in requests:
86+
h0.handle(request)
17387

17488
### OUTPUT ###
175-
# request 2 handled in handler 1
176-
# request 5 handled in handler 1
177-
# request 14 handled in handler 2
178-
# request 22 handled in handler 3
179-
# request 18 handled in handler 2
180-
# request 3 handled in handler 1
89+
# request 2 handled in handler 0
90+
# request 5 handled in handler 0
91+
# request 14 handled in handler 1
92+
# request 22 handled in handler 2
93+
# request 18 handled in handler 1
94+
# request 3 handled in handler 0
18195
# end of chain, no handler for 35
182-
# request 27 handled in handler 3
96+
# request 27 handled in handler 2
18397
# request 20 handled in handler 2
184-
# ------------------------------
185-
# request 2 handled in coroutine 1
186-
# request 5 handled in coroutine 1
187-
# request 14 handled in coroutine 2
188-
# request 22 handled in coroutine 3
189-
# request 18 handled in coroutine 2
190-
# request 3 handled in coroutine 1
191-
# end of chain, no coroutine for 35
192-
# request 27 handled in coroutine 3
193-
# request 20 handled in coroutine 2
194-
# (0.2369999885559082, 0.16199994087219238)

0 commit comments

Comments
 (0)