diff --git a/README.md b/README.md index 0a491fd..4a243da 100644 --- a/README.md +++ b/README.md @@ -39,3 +39,6 @@ and divided into three groups. - [Flyweight](structural/flyweight/README.md) - [Proxy](structural/proxy/README.md) +### [Behavioral Patterns](behavioral/README.md) + +- [Chain of Responsibility](behavioral/chain_responsibility/README.md) diff --git a/behavioral/README.md b/behavioral/README.md new file mode 100644 index 0000000..c320506 --- /dev/null +++ b/behavioral/README.md @@ -0,0 +1,7 @@ +# Behavioral Patterns + +Behavioral design patterns are concerned with algorithms and the assignment of responsibilities between objects. + +## Patterns + +- [Chain of Responsibility](chain_responsibility/README.md) \ No newline at end of file diff --git a/behavioral/__init__.py b/behavioral/__init__.py new file mode 100644 index 0000000..8fba3a3 --- /dev/null +++ b/behavioral/__init__.py @@ -0,0 +1,4 @@ +""" +Behavioral design patterns are concerned with algorithms and the assignment of +responsibilities between objects. +""" diff --git a/behavioral/chain_responsibility/README.md b/behavioral/chain_responsibility/README.md new file mode 100644 index 0000000..32cebbb --- /dev/null +++ b/behavioral/chain_responsibility/README.md @@ -0,0 +1,72 @@ +# Chain of Responsibility + +Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain. + +## Problem + +Imagine that you’re working on an online ordering system. You want to restrict access to the system so only authenticated users can create orders. Also, users who have administrative permissions must have full access to all orders. + +After a bit of planning, you realized that these checks must be performed sequentially. The application can attempt + to authenticate a user to the system whenever it receives a request that contains the user’s credentials. However, if those credentials aren't correct and authentication fails, there’s no reason to proceed with any other checks. + +During the next few months, you implemented several more of those sequential checks. + +- One of your colleagues suggested that it’s unsafe to pass raw data straight to the ordering system. So you added an + extra validation step to sanitize the data in a request. + +- Later, somebody noticed that the system is vulnerable to brute force password cracking. To negate this, you promptly + added a check that filters repeated failed requests coming from the same IP address. + +- Someone else suggested that you could speed up the system by returning cached results on repeated requests + containing the same data. Hence, you added another check which lets the request pass through to the system only if there’s no suitable cached response. + + The code of the checks, which had already looked like a mess, became more and more bloated as you added each new feature. Changing one check sometimes affected the others. Worst of all, when you tried to reuse the checks to protect other components of the system, you had to duplicate some of the code since those components required some of the checks, but not all of them. + +The system became very hard to comprehend and expensive to maintain. You struggled with the code for a while, until one day you decided to refactor the whole thing. + +## Solution + +Like many other behavioral design patterns, the Chain of Responsibility relies on transforming particular behaviors into stand-alone objects called handlers. In our case, each check should be extracted to its own class with a single method that performs the check. The request, along with its data, is passed to this method as an argument. + +The pattern suggests that you link these handlers into a chain. Each linked handler has a field for storing a reference to the next handler in the chain. In addition to processing a request, handlers pass the request further along the chain. The request travels along the chain until all handlers have had a chance to process it. + +Here’s the best part: a handler can decide not to pass the request further down the chain and effectively stop any further processing. + +In our example with ordering systems, a handler performs the processing and then decides whether to pass the request further down the chain. Assuming the request contains the right data, all the handlers can execute their primary behavior, whether it’s authentication checks or caching. + +However, there’s a slightly different approach (and it’s a bit more canonical) in which, upon receiving a request, a handler decides whether it can process it. If it can, it doesn't pass the request any further. So it’s either only one handler that processes the request or none at all. This approach is very common when dealing with events in stacks of elements within a graphical user interface. + +For instance, when a user clicks a button, the event propagates through the chain of GUI elements that starts with the button, goes along its containers (like forms or panels), and ends up with the main application window. The event is processed by the first element in the chain that’s capable of handling it. This example is also noteworthy because it shows that a chain can always be extracted from an object tree. + +It’s crucial that all handler classes implement the same interface. Each concrete handler should only care about the following one having the execute method. This way you can compose chains at runtime, using various handlers without coupling your code to their concrete classes. + +## How to Implement + +1. Declare the handler interface and describe the signature of a method for handling requests. + + Decide how the client will pass the request data into the method. The most flexible way is to convert the request + into an object and pass it to the handling method as an argument. + +2. To eliminate duplicate boilerplate code in concrete handlers, it might be worth creating an abstract base handler + class, derived from the handler interface. + + This class should have a field for storing a reference to the next handler in the chain. Consider making the class immutable. However, if you plan to modify chains at runtime, you need to define a setter for altering the value of the reference field. + + You can also implement the convenient default behavior for the handling method, which is to forward the request to the next object unless there’s none left. Concrete handlers will be able to use this behavior by calling the parent method. + +1. One by one create concrete handler subclasses and implement their handling methods. Each handler should make two decisions when receiving a request: + + - Whether it’ll process the request. + - Whether it’ll pass the request along the chain. + +1. The client may either assemble chains on its own or receive pre-built chains from other objects. In the latter case, you must implement some factory classes to build chains according to the configuration or environment settings. + +1. The client may trigger any handler in the chain, not just the first one. The request will be passed along the chain until some handler refuses to pass it further or until it reaches the end of the chain. + +1. Due to the dynamic nature of the chain, the client should be ready to handle the following scenarios: + + - The chain may consist of a single link. + - Some requests may not reach the end of the chain. + - Others may reach the end of the chain unhandled. + + \ No newline at end of file diff --git a/behavioral/chain_responsibility/__init__.py b/behavioral/chain_responsibility/__init__.py new file mode 100644 index 0000000..bbd1914 --- /dev/null +++ b/behavioral/chain_responsibility/__init__.py @@ -0,0 +1,5 @@ +""" +Lets you pass requests along a chain of handlers. Upon receiving a request, +each handler decides either to process the request or to pass it to the next +handler in the chain. +""" diff --git a/behavioral/chain_responsibility/abstract_handler.py b/behavioral/chain_responsibility/abstract_handler.py new file mode 100644 index 0000000..e6c2b53 --- /dev/null +++ b/behavioral/chain_responsibility/abstract_handler.py @@ -0,0 +1,27 @@ +from abc import abstractmethod +from typing import Any, Optional + +from behavioral.chain_responsibility.handler import Handler + + +class AbstractHandler(Handler): + """ + The default chaining behavior can be implemented inside a base handler + class. + """ + + _next_handler: Handler = None + + def set_next(self, handler: Handler) -> Handler: + self._next_handler = handler + # Returning a handler from here will let us link handlers in a + # convenient way like this: + # monkey.set_next(squirrel).set_next(dog) + return handler + + @abstractmethod + def handle(self, request: Any) -> Optional[str]: + if self._next_handler: + return self._next_handler.handle(request) + + return None diff --git a/behavioral/chain_responsibility/concrete_handlers.py b/behavioral/chain_responsibility/concrete_handlers.py new file mode 100644 index 0000000..3290a8d --- /dev/null +++ b/behavioral/chain_responsibility/concrete_handlers.py @@ -0,0 +1,28 @@ +""" +All Concrete Handlers either handle a request or pass it to the next handler in +the chain. +""" +from typing import Optional, Any + +from behavioral.chain_responsibility.abstract_handler import AbstractHandler + + +class MonkeyHandler(AbstractHandler): + def handle(self, request: Any) -> Optional[str]: + if request == 'Banana': + return f"Monkey: I'll eat the {request}" + return super().handle(request) + + +class SquirrelHandler(AbstractHandler): + def handle(self, request: Any) -> Optional[str]: + if request == 'Nut': + return f"Squirrel: I'll eat the {request}" + return super().handle(request) + + +class DogHandler(AbstractHandler): + def handle(self, request: Any) -> Optional[str]: + if request == 'MeatBall': + return f"Dog: I'll eat the {request}" + return super().handle(request) diff --git a/behavioral/chain_responsibility/handler.py b/behavioral/chain_responsibility/handler.py new file mode 100644 index 0000000..247dfff --- /dev/null +++ b/behavioral/chain_responsibility/handler.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Optional, Any + + +class Handler(ABC): + """ + The Handler interface declares a method for building the chain of handlers. + It also declares a method for executing a request. + """ + + @abstractmethod + def set_next(self, handler: Handler) -> Handler: + pass + + @abstractmethod + def handle(self, request: Any) -> Optional[str]: + pass diff --git a/behavioral/chain_responsibility/main.py b/behavioral/chain_responsibility/main.py new file mode 100644 index 0000000..c5cbbcd --- /dev/null +++ b/behavioral/chain_responsibility/main.py @@ -0,0 +1,35 @@ +from behavioral.chain_responsibility.concrete_handlers import MonkeyHandler, SquirrelHandler, DogHandler +from behavioral.chain_responsibility.handler import Handler + + +def client_code(handler: Handler): + """ + The client code is usually suited to work with a single handler. In most + cases, it is not even aware that the handler is part of a chain. + """ + + for food in ["Nut", "Banana", "Cup of coffee"]: + print(f"\nClient: Who wants a {food}?") + result = handler.handle(food) + + if result: + print(f" {result}", end="") + else: + print(f" {food} was left untouched.", end="") + + +if __name__ == '__main__': + monkey = MonkeyHandler() + squirrel = SquirrelHandler() + dog = DogHandler() + + monkey.set_next(squirrel).set_next(dog) + + # The client should be able to send a request to any handler, not just the + # first one in the chain. + print("Chain: Monkey > Squirrel > Dog") + client_code(monkey) + print("\n") + + print("Subchain: Squirrel > Dog") + client_code(squirrel) diff --git a/creational/README.md b/creational/README.md index 6776d99..da76999 100644 --- a/creational/README.md +++ b/creational/README.md @@ -2,6 +2,8 @@ Creational patterns provide various object creation mechanisms, which increase flexibility and reuse of existing code. +## Patterns + - [Factory Method](factory_method/README.md) - [Abstract Factory](abstract_factory/README.md) - [Builder](builder/README.md) diff --git a/structural/README.md b/structural/README.md index 69ecb16..084ffa4 100644 --- a/structural/README.md +++ b/structural/README.md @@ -2,6 +2,8 @@ Structural patterns explain how to assemble objects and classes into larger structures while keeping these structures flexible and efficient. +## Patterns + - [Adapter](adapter/README.md) - [Bridge](bridge/README.md) - [Composite](composite/README.md)