Skip to content

Commit ad07247

Browse files
authored
Merge pull request #10 from taggedzi/Chain-of-Responsibility---Improvements
Chain of responsibility improvements
2 parents 5d9661c + f5016cc commit ad07247

File tree

3 files changed

+243
-35
lines changed

3 files changed

+243
-35
lines changed

chunks/behavioral_chain_of_responsibility.md

Lines changed: 126 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,74 +10,180 @@ Behavioral Pattern: Chain of Responsibility
1010
Lets you pass requests along a chain of handlers, where each handler decides
1111
whether to process the request or pass it along.
1212
Promotes loose coupling between sender and receiver.
13+
14+
Class Diagram:
15+
16+
+------------------+
17+
| Handler (ABC) |
18+
+------------------+
19+
| - _next_handler |
20+
+------------------+
21+
| + set_next() |
22+
| + handle() |
23+
| + pass_to_next() |
24+
+------------------+
25+
^
26+
|
27+
+-------------------+ +-------------------+ +----------------+
28+
| MonkeyHandler | | SquirrelHandler | | DogHandler |
29+
+-------------------+ +-------------------+ +----------------+
30+
| + handle() | | + handle() | | + handle() |
31+
+-------------------+ +-------------------+ +----------------+
32+
33+
Runtime Chain Flow:
34+
35+
Client
36+
|
37+
v
38+
MonkeyHandler --> SquirrelHandler --> CatHandler --> DogHandler --> None
39+
|
40+
[Request: "Fish"]
41+
|
42+
v
43+
CatHandler handles it!
1344
"""
1445

1546
from __future__ import annotations
1647
from abc import ABC, abstractmethod
1748
from typing import Optional
49+
import unittest
50+
1851

1952
class Handler(ABC):
20-
"""Base Handler class with chaining support."""
53+
"""
54+
The base Handler class declares the interface for handling requests and for
55+
setting the next handler in the chain.
56+
"""
2157

2258
_next_handler: Optional[Handler] = None
2359

2460
def set_next(self, handler: Handler) -> Handler:
61+
"""
62+
Set the next handler in the chain and return the handler to allow chaining.
63+
"""
2564
self._next_handler = handler
2665
return handler
2766

2867
@abstractmethod
2968
def handle(self, request: str) -> Optional[str]:
69+
"""
70+
Handle the request or pass it to the next handler in the chain.
71+
"""
3072
pass
3173

74+
def pass_to_next(self, request: str) -> Optional[str]:
75+
"""
76+
Utility method to pass request to the next handler, if any.
77+
"""
78+
if self._next_handler:
79+
print(f"{self.__class__.__name__}: Can't handle {request}, passing to next...")
80+
return self._next_handler.handle(request)
81+
print(f"{self.__class__.__name__}: No next handler for {request}.")
82+
return None
83+
84+
3285
class MonkeyHandler(Handler):
86+
"""Handler that processes requests related to Bananas."""
87+
3388
def handle(self, request: str) -> Optional[str]:
3489
if request == "Banana":
3590
return "Monkey: I'll eat the Banana."
36-
elif self._next_handler:
37-
return self._next_handler.handle(request)
38-
return None
91+
return self.pass_to_next(request)
92+
3993

4094
class SquirrelHandler(Handler):
95+
"""Handler that processes requests related to Nuts."""
96+
4197
def handle(self, request: str) -> Optional[str]:
4298
if request == "Nut":
4399
return "Squirrel: I'll eat the Nut."
44-
elif self._next_handler:
45-
return self._next_handler.handle(request)
46-
return None
100+
return self.pass_to_next(request)
101+
47102

48103
class DogHandler(Handler):
104+
"""Handler that processes requests related to Meat."""
105+
49106
def handle(self, request: str) -> Optional[str]:
50107
if request == "Meat":
51108
return "Dog: I'll eat the Meat."
52-
elif self._next_handler:
53-
return self._next_handler.handle(request)
54-
return None
109+
return self.pass_to_next(request)
110+
111+
112+
class CatHandler(Handler):
113+
"""Handler that processes requests related to Fish."""
114+
115+
def handle(self, request: str) -> Optional[str]:
116+
if request == "Fish":
117+
return "Cat: I'll eat the Fish."
118+
return self.pass_to_next(request)
119+
120+
121+
# Unit Tests
122+
class TestChainOfResponsibility(unittest.TestCase):
123+
"""Test cases for the Chain of Responsibility pattern."""
124+
def setUp(self):
125+
self.monkey = MonkeyHandler()
126+
self.squirrel = SquirrelHandler()
127+
self.cat = CatHandler()
128+
self.dog = DogHandler()
129+
self.monkey.set_next(self.squirrel).set_next(self.cat).set_next(self.dog)
130+
131+
def test_monkey_handler(self):
132+
"""Test that the MonkeyHandler handles a request."""
133+
self.assertEqual(self.monkey.handle("Banana"), "Monkey: I'll eat the Banana.")
134+
135+
def test_squirrel_handler(self):
136+
"""Test that the SquirrelHandler handles a request."""
137+
self.assertEqual(self.monkey.handle("Nut"), "Squirrel: I'll eat the Nut.")
138+
139+
def test_cat_handler(self):
140+
"""Test that the CatHandler handles a request."""
141+
self.assertEqual(self.monkey.handle("Fish"), "Cat: I'll eat the Fish.")
142+
143+
def test_dog_handler(self):
144+
"""Test that the DogHandler handles a request."""
145+
self.assertEqual(self.monkey.handle("Meat"), "Dog: I'll eat the Meat.")
146+
147+
def test_unhandled_request(self):
148+
"""Test that an unhandled request is passed to the next handler."""
149+
self.assertIsNone(self.monkey.handle("Apple"))
55150

56151
# Example usage
57152
if __name__ == "__main__":
153+
# Create handlers
58154
monkey = MonkeyHandler()
59155
squirrel = SquirrelHandler()
60156
dog = DogHandler()
157+
cat = CatHandler()
61158

62-
monkey.set_next(squirrel).set_next(dog)
159+
# Dynamically construct chain
160+
monkey.set_next(squirrel).set_next(cat).set_next(dog)
63161

64-
for food in ["Nut", "Banana", "Meat", "Apple"]:
65-
print(f"Client: Who wants a {food}?")
162+
# Test inputs
163+
for food in ["Nut", "Banana", "Meat", "Fish", "Apple"]:
164+
print(f"\nClient: Who wants a {food}?")
66165
result = monkey.handle(food)
67166
if result:
68167
print(result)
69168
else:
70169
print(f"No one wants the {food}.")
170+
171+
# Unit Tests
172+
unittest.main()
173+
71174
```
72175

73176
## Summary
74-
Implementation of the Chain of Responsibility pattern in Python.
177+
Implementation of the Chain of Responsibility pattern in Python, demonstrating a request-passing mechanism through a series of handlers.
75178

76179
## Docstrings
77-
- Base Handler class with chaining support.
78-
- Sets the next handler in the chain and returns the next handler.
79-
- Abstract method to handle requests. Must be implemented by subclasses.
80-
- Handler for monkeys that can eat bananas.
81-
- Handler for squirrels that can eat nuts.
82-
- Handler for dogs that can eat meat.
180+
- The base Handler class declares the interface for handling requests and for setting the next handler in the chain.
181+
- Set the next handler in the chain and return the handler to allow chaining.
182+
- Handle the request or pass it to the next handler in the chain.
183+
- Utility method to pass request to the next handler, if any.
184+
- Handler that processes requests related to Bananas.
185+
- Handler that processes requests related to Nuts.
186+
- Handler that processes requests related to Meat.
187+
- Handler that processes requests related to Fish.
188+
- Test cases for the Chain of Responsibility pattern.
83189

patterns/behavioral/chain_of_responsibility.py

Lines changed: 116 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,61 +4,163 @@
44
Lets you pass requests along a chain of handlers, where each handler decides
55
whether to process the request or pass it along.
66
Promotes loose coupling between sender and receiver.
7+
8+
Class Diagram:
9+
10+
+------------------+
11+
| Handler (ABC) |
12+
+------------------+
13+
| - _next_handler |
14+
+------------------+
15+
| + set_next() |
16+
| + handle() |
17+
| + pass_to_next() |
18+
+------------------+
19+
^
20+
|
21+
+-------------------+ +-------------------+ +----------------+
22+
| MonkeyHandler | | SquirrelHandler | | DogHandler |
23+
+-------------------+ +-------------------+ +----------------+
24+
| + handle() | | + handle() | | + handle() |
25+
+-------------------+ +-------------------+ +----------------+
26+
27+
Runtime Chain Flow:
28+
29+
Client
30+
|
31+
v
32+
MonkeyHandler --> SquirrelHandler --> CatHandler --> DogHandler --> None
33+
|
34+
[Request: "Fish"]
35+
|
36+
v
37+
CatHandler handles it!
738
"""
839

940
from __future__ import annotations
1041
from abc import ABC, abstractmethod
1142
from typing import Optional
43+
import unittest
44+
1245

1346
class Handler(ABC):
14-
"""Base Handler class with chaining support."""
47+
"""
48+
The base Handler class declares the interface for handling requests and for
49+
setting the next handler in the chain.
50+
"""
1551

1652
_next_handler: Optional[Handler] = None
1753

1854
def set_next(self, handler: Handler) -> Handler:
55+
"""
56+
Set the next handler in the chain and return the handler to allow chaining.
57+
"""
1958
self._next_handler = handler
2059
return handler
2160

2261
@abstractmethod
2362
def handle(self, request: str) -> Optional[str]:
63+
"""
64+
Handle the request or pass it to the next handler in the chain.
65+
"""
2466
pass
2567

68+
def pass_to_next(self, request: str) -> Optional[str]:
69+
"""
70+
Utility method to pass request to the next handler, if any.
71+
"""
72+
if self._next_handler:
73+
print(f"{self.__class__.__name__}: Can't handle {request}, passing to next...")
74+
return self._next_handler.handle(request)
75+
print(f"{self.__class__.__name__}: No next handler for {request}.")
76+
return None
77+
78+
2679
class MonkeyHandler(Handler):
80+
"""Handler that processes requests related to Bananas."""
81+
2782
def handle(self, request: str) -> Optional[str]:
2883
if request == "Banana":
2984
return "Monkey: I'll eat the Banana."
30-
elif self._next_handler:
31-
return self._next_handler.handle(request)
32-
return None
85+
return self.pass_to_next(request)
86+
3387

3488
class SquirrelHandler(Handler):
89+
"""Handler that processes requests related to Nuts."""
90+
3591
def handle(self, request: str) -> Optional[str]:
3692
if request == "Nut":
3793
return "Squirrel: I'll eat the Nut."
38-
elif self._next_handler:
39-
return self._next_handler.handle(request)
40-
return None
94+
return self.pass_to_next(request)
95+
4196

4297
class DogHandler(Handler):
98+
"""Handler that processes requests related to Meat."""
99+
43100
def handle(self, request: str) -> Optional[str]:
44101
if request == "Meat":
45102
return "Dog: I'll eat the Meat."
46-
elif self._next_handler:
47-
return self._next_handler.handle(request)
48-
return None
103+
return self.pass_to_next(request)
104+
105+
106+
class CatHandler(Handler):
107+
"""Handler that processes requests related to Fish."""
108+
109+
def handle(self, request: str) -> Optional[str]:
110+
if request == "Fish":
111+
return "Cat: I'll eat the Fish."
112+
return self.pass_to_next(request)
113+
114+
115+
# Unit Tests
116+
class TestChainOfResponsibility(unittest.TestCase):
117+
"""Test cases for the Chain of Responsibility pattern."""
118+
def setUp(self):
119+
self.monkey = MonkeyHandler()
120+
self.squirrel = SquirrelHandler()
121+
self.cat = CatHandler()
122+
self.dog = DogHandler()
123+
self.monkey.set_next(self.squirrel).set_next(self.cat).set_next(self.dog)
124+
125+
def test_monkey_handler(self):
126+
"""Test that the MonkeyHandler handles a request."""
127+
self.assertEqual(self.monkey.handle("Banana"), "Monkey: I'll eat the Banana.")
128+
129+
def test_squirrel_handler(self):
130+
"""Test that the SquirrelHandler handles a request."""
131+
self.assertEqual(self.monkey.handle("Nut"), "Squirrel: I'll eat the Nut.")
132+
133+
def test_cat_handler(self):
134+
"""Test that the CatHandler handles a request."""
135+
self.assertEqual(self.monkey.handle("Fish"), "Cat: I'll eat the Fish.")
136+
137+
def test_dog_handler(self):
138+
"""Test that the DogHandler handles a request."""
139+
self.assertEqual(self.monkey.handle("Meat"), "Dog: I'll eat the Meat.")
140+
141+
def test_unhandled_request(self):
142+
"""Test that an unhandled request is passed to the next handler."""
143+
self.assertIsNone(self.monkey.handle("Apple"))
49144

50145
# Example usage
51146
if __name__ == "__main__":
147+
# Create handlers
52148
monkey = MonkeyHandler()
53149
squirrel = SquirrelHandler()
54150
dog = DogHandler()
151+
cat = CatHandler()
55152

56-
monkey.set_next(squirrel).set_next(dog)
153+
# Dynamically construct chain
154+
monkey.set_next(squirrel).set_next(cat).set_next(dog)
57155

58-
for food in ["Nut", "Banana", "Meat", "Apple"]:
59-
print(f"Client: Who wants a {food}?")
156+
# Test inputs
157+
for food in ["Nut", "Banana", "Meat", "Fish", "Apple"]:
158+
print(f"\nClient: Who wants a {food}?")
60159
result = monkey.handle(food)
61160
if result:
62161
print(result)
63162
else:
64-
print(f"No one wants the {food}.")
163+
print(f"No one wants the {food}.")
164+
165+
# Unit Tests
166+
unittest.main()

summary_index.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"file": "behavioral/chain_of_responsibility.py",
44
"chunk": "behavioral_chain_of_responsibility.md",
55
"pattern": "Chain Of Responsibility",
6-
"summary": "Implementation of the Chain of Responsibility pattern in Python."
6+
"summary": "Implementation of the Chain of Responsibility pattern in Python, demonstrating a request-passing mechanism through a series of handlers."
77
},
88
{
99
"file": "behavioral/command.py",

0 commit comments

Comments
 (0)