diff --git a/README.md b/README.md index 640d273..aa817a1 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,7 @@ and divided into three groups. - [Iterator](behavioral/iterator/README.md) - [Mediator](behavioral/mediator/README.md) - [Memento](behavioral/memento/README.md) +- [Observer](behavioral/observer/README.md) ## Resources diff --git a/behavioral/README.md b/behavioral/README.md index 10b3715..99b8de5 100644 --- a/behavioral/README.md +++ b/behavioral/README.md @@ -9,3 +9,4 @@ Behavioral design patterns are concerned with algorithms and the assignment of r - [Iterator](iterator/README.md) - [Mediator](mediator/README.md) - [Memento](memento/README.md) +- [Observer](observer/README.md) diff --git a/behavioral/chain_responsibility/concrete_handlers.py b/behavioral/chain_responsibility/concrete_handlers.py index 3290a8d..994a390 100644 --- a/behavioral/chain_responsibility/concrete_handlers.py +++ b/behavioral/chain_responsibility/concrete_handlers.py @@ -9,20 +9,20 @@ from behavioral.chain_responsibility.abstract_handler import AbstractHandler class MonkeyHandler(AbstractHandler): def handle(self, request: Any) -> Optional[str]: - if request == 'Banana': + 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': + 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': + if request == "MeatBall": return f"Dog: I'll eat the {request}" return super().handle(request) diff --git a/behavioral/chain_responsibility/main.py b/behavioral/chain_responsibility/main.py index c5cbbcd..71a466b 100644 --- a/behavioral/chain_responsibility/main.py +++ b/behavioral/chain_responsibility/main.py @@ -1,4 +1,8 @@ -from behavioral.chain_responsibility.concrete_handlers import MonkeyHandler, SquirrelHandler, DogHandler +from behavioral.chain_responsibility.concrete_handlers import ( + MonkeyHandler, + SquirrelHandler, + DogHandler, +) from behavioral.chain_responsibility.handler import Handler @@ -18,7 +22,7 @@ def client_code(handler: Handler): print(f" {food} was left untouched.", end="") -if __name__ == '__main__': +if __name__ == "__main__": monkey = MonkeyHandler() squirrel = SquirrelHandler() dog = DogHandler() diff --git a/behavioral/command/commands.py b/behavioral/command/commands.py index e950e1e..4f6c9f0 100644 --- a/behavioral/command/commands.py +++ b/behavioral/command/commands.py @@ -12,7 +12,8 @@ class SimpleCommand(Command): def execute(self) -> None: print( - f"SimpleCommand: See, I can do simple things like printing ({self._payload})") + f"SimpleCommand: See, I can do simple things like printing ({self._payload})" + ) class ComplexCommand(Command): @@ -36,6 +37,8 @@ class ComplexCommand(Command): Commands can delegate to any methods of a receiver. """ - print("ComplexCommand: Complex stuff should be done by a receiver object", end="") + print( + "ComplexCommand: Complex stuff should be done by a receiver object", end="" + ) self._receiver.do_something(self._a) self._receiver.do_something_else(self._b) diff --git a/behavioral/command/main.py b/behavioral/command/main.py index 00286c2..81196ff 100644 --- a/behavioral/command/main.py +++ b/behavioral/command/main.py @@ -5,12 +5,11 @@ from behavioral.command.commands import SimpleCommand, ComplexCommand from behavioral.command.invoker import Invoker from behavioral.command.receiver import Receiver -if __name__ == '__main__': +if __name__ == "__main__": invoker = Invoker() invoker.set_on_start(SimpleCommand("Say hi")) receiver = Receiver() - invoker.set_on_finish(ComplexCommand( - receiver, "Send email", "Save report")) + invoker.set_on_finish(ComplexCommand(receiver, "Send email", "Save report")) invoker.do_something_important() diff --git a/behavioral/memento/concrete_memento.py b/behavioral/memento/concrete_memento.py index c7daba7..c25912c 100644 --- a/behavioral/memento/concrete_memento.py +++ b/behavioral/memento/concrete_memento.py @@ -4,7 +4,6 @@ from behavioral.memento.memento import Memento class ConcreteMemento(Memento): - def __init__(self, state: str) -> None: self._state = state self._date = str(datetime.now())[:19] diff --git a/behavioral/observer/README.md b/behavioral/observer/README.md new file mode 100644 index 0000000..0256c64 --- /dev/null +++ b/behavioral/observer/README.md @@ -0,0 +1,47 @@ +# Observer + +Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing. + +## Problem + +Imagine that you have two types of objects: a `Customer` and a `Store`. The customer is very interested in a particular brand of product which should become available in the store very soon. + +The customer could visit the store every day and check product availability. But while the product is still en route, most of these trips would be pointless. + +On the other hand, the store could send tons of emails (which might be considered spam) to all customers each time a new product becomes available. This would save some customers from endless trips to the store. At the same time, it’d upset other customers who aren't interested in new products. + +It looks like we’ve got a conflict. Either the customer wastes time checking product availability or the store wastes resources notifying the wrong customers. + +## Solution + +The object that has some interesting state is often called subject, but since it’s also going to notify other objects about the changes to its state, we’ll call it publisher. All other objects that want to track changes to the publisher’s state are called subscribers. + +The Observer pattern suggests that you add a subscription mechanism to the publisher class so individual objects can subscribe to or unsubscribe from a stream of events coming from that publisher. Fear not! Everything isn’t as complicated as it sounds. In reality, this mechanism consists of 1) an array field for storing a list of references to subscriber objects and 2) several public methods which allow adding subscribers to and removing them from that list. + +Now, whenever an important event happens to the publisher, it goes over its subscribers and calls the specific notification method on their objects. + +Real apps might have dozens of different subscriber classes that are interested in tracking events of the same publisher class. You wouldn't want to couple the publisher to all of those classes. Besides, you might not even know about some of them beforehand if your publisher class is supposed to be used by other people. + +That’s why it’s crucial that all subscribers implement the same interface and that the publisher communicates with them only via that interface. This interface should declare the notification method along with a set of parameters that the publisher can use to pass some contextual data along with the notification. + +If your app has several different types of publishers and you want to make your subscribers compatible with all of them, you can go even further and make all publishers follow the same interface. This interface would only need to describe a few subscription methods. The interface would allow subscribers to observe publishers’ states without coupling to their concrete classes. + +## How to Implement + +1. Look over your business logic and try to break it down into two parts: the core functionality, independent from other code, will act as the publisher; the rest will turn into a set of subscriber classes. + +1. Declare the subscriber interface. At a bare minimum, it should declare a single update method. + +1. Declare the publisher interface and describe a pair of methods for adding a subscriber object to and removing it from the list. Remember that publishers must work with subscribers only via the subscriber interface. + +1. Decide where to put the actual subscription list and the implementation of subscription methods. Usually, this code looks the same for all types of publishers, so the obvious place to put it is in an abstract class derived directly from the publisher interface. Concrete publishers extend that class, inheriting the subscription behavior. + + However, if you’re applying the pattern to an existing class hierarchy, consider an approach based on composition: put the subscription logic into a separate object, and make all real publishers use it. + +1. Create concrete publisher classes. Each time something important happens inside a publisher, it must notify all its subscribers. + +1. Implement the update notification methods in concrete subscriber classes. Most subscribers would need some context data about the event. It can be passed as an argument of the notification method. + + But there’s another option. Upon receiving a notification, the subscriber can fetch any data directly from the notification. In this case, the publisher must pass itself via the update method. The less flexible option is to link a publisher to the subscriber permanently via the constructor. + +1. The client must create all necessary subscribers and register them with proper publishers. \ No newline at end of file diff --git a/behavioral/observer/__init__.py b/behavioral/observer/__init__.py new file mode 100644 index 0000000..ee00f9e --- /dev/null +++ b/behavioral/observer/__init__.py @@ -0,0 +1,4 @@ +""" +Lets you define a subscription mechanism to notify multiple objects about any +events that happen to the object they’re observing. +""" diff --git a/behavioral/observer/concrete_observers.py b/behavioral/observer/concrete_observers.py new file mode 100644 index 0000000..63c755e --- /dev/null +++ b/behavioral/observer/concrete_observers.py @@ -0,0 +1,18 @@ +""" +Concrete Observers react to the updates issued by the Subject they had been +attached to. +""" +from behavioral.observer.observer import Observer +from behavioral.observer.subject import Subject + + +class ConcreteObserverA(Observer): + def update(self, subject: Subject) -> None: + if subject.state < 3: + print("ConcreteObserverA: Reacted to the event") + + +class ConcreteObserverB(Observer): + def update(self, subject: Subject) -> None: + if subject.state == 0 or subject.state >= 2: + print("ConcreteObserverB: Reacted to the event") diff --git a/behavioral/observer/concrete_subject.py b/behavioral/observer/concrete_subject.py new file mode 100644 index 0000000..fb04d2c --- /dev/null +++ b/behavioral/observer/concrete_subject.py @@ -0,0 +1,61 @@ +from random import randrange +from typing import List, Optional + +from behavioral.observer.observer import Observer +from behavioral.observer.subject import Subject + + +class ConcreteSubject(Subject): + """ + The Subject owns some important state and notifies observers when the state + changes. + """ + + """ + For the sake of simplicity, the Subject's state, essential to all + subscribers, is stored in this variable. + """ + _state: Optional[int] = None + + """ + List of subscribers. In real life, the list of subscribers can be stored + more comprehensively (categorized by event type, etc.). + """ + _observers: List[Observer] = [] + + @property + def state(self) -> int: + return self._state + + def attach(self, observer: Observer) -> None: + print("Subject: Attached an observer.") + self._observers.append(observer) + + def detach(self, observer: Observer) -> None: + self._observers.remove(observer) + + """ + The subscription management methods. + """ + + def notify(self) -> None: + """ + Trigger an update in each subscriber. + """ + print("Subject: Notifying observers...") + for observer in self._observers: + observer.update(self) + + def some_business_logic(self) -> None: + """ + Usually, the subscription logic is only a fraction of what a Subject can + really do. Subjects commonly hold some important business logic, that + triggers a notification method whenever something important is about to + happen (or after it). + """ + + print("\nSubject: I'm doing something important.") + self._state = randrange(0, 10) + + print(f"Subject: My state has just changed to: {self._state}") + self.notify() diff --git a/behavioral/observer/main.py b/behavioral/observer/main.py new file mode 100644 index 0000000..34a0fa1 --- /dev/null +++ b/behavioral/observer/main.py @@ -0,0 +1,23 @@ +"""The client code""" + +from behavioral.observer.concrete_observers import ConcreteObserverA, ConcreteObserverB + + +from behavioral.observer.concrete_subject import ConcreteSubject + +subject = ConcreteSubject() + + +observer_a = ConcreteObserverA() +subject.attach(observer_a) + +observer_b = ConcreteObserverB() +subject.attach(observer_b) + + +subject.some_business_logic() +subject.some_business_logic() + +subject.detach(observer_a) + +subject.some_business_logic() diff --git a/behavioral/observer/observer.py b/behavioral/observer/observer.py new file mode 100644 index 0000000..f3abbeb --- /dev/null +++ b/behavioral/observer/observer.py @@ -0,0 +1,16 @@ +from abc import ABC, abstractmethod + +from structural.proxy.subject import Subject + + +class Observer(ABC): + """ + The Observer interface declares the update method, used by subjects. + """ + + @abstractmethod + def update(self, subject: Subject) -> None: + """ + Receive update from subject. + """ + pass diff --git a/behavioral/observer/subject.py b/behavioral/observer/subject.py new file mode 100644 index 0000000..ae1f6c8 --- /dev/null +++ b/behavioral/observer/subject.py @@ -0,0 +1,35 @@ +from abc import ABC, abstractmethod + +from behavioral.observer.observer import Observer + + +class Subject(ABC): + """ + The Subject interface declares a set of methods for managing subscribers. + """ + + @property + @abstractmethod + def state(self) -> int: + pass + + @abstractmethod + def attach(self, observer: Observer) -> None: + """ + Attach an observer to the subject. + """ + pass + + @abstractmethod + def detach(self, observer: Observer) -> None: + """ + Detach an observer to the subject. + """ + pass + + @abstractmethod + def notify(self) -> None: + """ + Notify all observers about an event. + """ + pass diff --git a/creational/abstract_factory/factories.py b/creational/abstract_factory/factories.py index defbc5e..48d16b2 100644 --- a/creational/abstract_factory/factories.py +++ b/creational/abstract_factory/factories.py @@ -1,6 +1,10 @@ from creational.abstract_factory.AbstractFactory import AbstractFactory -from creational.abstract_factory.products import ConcreteProductA1, \ - ConcreteProductB1, ConcreteProductA2, ConcreteProductB2 +from creational.abstract_factory.products import ( + ConcreteProductA1, + ConcreteProductB1, + ConcreteProductA2, + ConcreteProductB2, +) class ConcreteFactory1(AbstractFactory): diff --git a/creational/abstract_factory/main.py b/creational/abstract_factory/main.py index dc0180c..9567c21 100644 --- a/creational/abstract_factory/main.py +++ b/creational/abstract_factory/main.py @@ -1,8 +1,7 @@ """The client code can work with any concrete factory class.""" from creational.abstract_factory.AbstractFactory import AbstractFactory -from creational.abstract_factory.factories import ConcreteFactory1, \ - ConcreteFactory2 +from creational.abstract_factory.factories import ConcreteFactory1, ConcreteFactory2 def client_code(factory: AbstractFactory) -> None: diff --git a/creational/factory_method/creators.py b/creational/factory_method/creators.py index 63f16cf..bca8aa7 100644 --- a/creational/factory_method/creators.py +++ b/creational/factory_method/creators.py @@ -4,8 +4,7 @@ product's type. """ from creational.factory_method.ICreator import ICreator -from creational.factory_method.products import ConcreteProduct1, \ - ConcreteProduct2 +from creational.factory_method.products import ConcreteProduct1, ConcreteProduct2 class ConcreteCreator1(ICreator): diff --git a/creational/factory_method/main.py b/creational/factory_method/main.py index c67f12d..ecd1a75 100644 --- a/creational/factory_method/main.py +++ b/creational/factory_method/main.py @@ -1,6 +1,5 @@ from creational.factory_method.ICreator import ICreator -from creational.factory_method.creators import ConcreteCreator1, \ - ConcreteCreator2 +from creational.factory_method.creators import ConcreteCreator1, ConcreteCreator2 def client_code(creator: ICreator) -> None: diff --git a/creational/prototype/components.py b/creational/prototype/components.py index a8f8fc4..9d2744d 100644 --- a/creational/prototype/components.py +++ b/creational/prototype/components.py @@ -39,8 +39,7 @@ class SomeComponent: # Then, let's clone the object itself, using the prepared clones of the # nested objects. - new = self.__class__( - self.some_int, some_list_of_objects, some_circular_ref) + new = self.__class__(self.some_int, some_list_of_objects, some_circular_ref) new.__dict__.update(self.__dict__) return new @@ -63,8 +62,7 @@ class SomeComponent: some_circular_ref = copy.deepcopy(self.some_circular_ref, memo) # Then, let's clone the object itself, using the prepared clones of the # nested objects. - new = self.__class__( - self.some_int, some_list_of_objects, some_circular_ref) + new = self.__class__(self.some_int, some_list_of_objects, some_circular_ref) new.__dict__ = copy.deepcopy(self.__dict__, memo) diff --git a/structural/bridge/main.py b/structural/bridge/main.py index cf4ab71..6449aa6 100644 --- a/structural/bridge/main.py +++ b/structural/bridge/main.py @@ -3,8 +3,10 @@ The client code should be able to work with any pre-configured abstraction- implementation combination. """ from structural.bridge.abstractions import Abstraction -from structural.bridge.implementations import ConcreteImplementationA, \ - ConcreteImplementationB +from structural.bridge.implementations import ( + ConcreteImplementationA, + ConcreteImplementationB, +) def client_code(abstraction: Abstraction) -> None: diff --git a/structural/decorator/main.py b/structural/decorator/main.py index 3c23d77..303952e 100644 --- a/structural/decorator/main.py +++ b/structural/decorator/main.py @@ -1,7 +1,9 @@ from structural.decorator.component import Component from structural.decorator.concrete_component import ConcreteComponent -from structural.decorator.concrete_decorators import ConcreteDecoratorA, \ - ConcreteDecoratorB +from structural.decorator.concrete_decorators import ( + ConcreteDecoratorA, + ConcreteDecoratorB, +) def client_code(component: Component): @@ -11,10 +13,10 @@ def client_code(component: Component): with. """ - print(f"RESULT: {component.operation()}", end='') + print(f"RESULT: {component.operation()}", end="") -if __name__ == '__main__': +if __name__ == "__main__": # This way the client code can support both simple components... simple = ConcreteComponent() print("Client: I've got a simple component:") diff --git a/structural/facade/facade.py b/structural/facade/facade.py index a631c08..a8cc009 100644 --- a/structural/facade/facade.py +++ b/structural/facade/facade.py @@ -25,12 +25,13 @@ class Facade: functionality of the subsystems. However, clients get only to a fraction of a subsystem's capabilities. """ - results = [] - results.append("Facade initializes subsystems:") - results.append(self._subsystem1.operation1()) - results.append(self._subsystem2.operation1()) - results.append("Facade orders subsystems to perform the action:") - results.append(self._subsystem1.operation_n()) - results.append(self._subsystem2.operation_z()) + results = [ + "Facade initializes subsystems:", + self._subsystem1.operation1(), + self._subsystem2.operation1(), + "Facade orders subsystems to perform the action:", + self._subsystem1.operation_n(), + self._subsystem2.operation_z(), + ] - return '\n'.join(results) + return "\n".join(results) diff --git a/structural/facade/main.py b/structural/facade/main.py index 8e6c417..263670d 100644 --- a/structural/facade/main.py +++ b/structural/facade/main.py @@ -10,10 +10,10 @@ def client_code(facade: Facade) -> None: subsystem. This approach lets you keep the complexity under control. """ - print(facade.operation(), end='') + print(facade.operation(), end="") -if __name__ == '__main__': +if __name__ == "__main__": # The client code may have some of the subsystem's objects already created. # In this case, it might be worthwhile to initialize the Facade with these # objects instead of letting the Facade create new instances. diff --git a/structural/flyweight/flyweight.py b/structural/flyweight/flyweight.py index ff7ee61..7b95b17 100644 --- a/structural/flyweight/flyweight.py +++ b/structural/flyweight/flyweight.py @@ -16,5 +16,4 @@ class Flyweight: def operation(self, unique_state: List[str]) -> None: s = json.dumps(self._shared_state) u = json.dumps(unique_state) - print(f"Flyweight: Displaying shared ({s}) and unique ({u}) state.", - end="") + print(f"Flyweight: Displaying shared ({s}) and unique ({u}) state.", end="") diff --git a/structural/flyweight/main.py b/structural/flyweight/main.py index 4e06067..883886d 100644 --- a/structural/flyweight/main.py +++ b/structural/flyweight/main.py @@ -1,9 +1,14 @@ from structural.flyweight.flyweight_factory import FlyweightFactory -def add_car_to_police_database(factory: FlyweightFactory, plates: str, - owner: str, brand: str, model: str, - color: str) -> None: +def add_car_to_police_database( + factory: FlyweightFactory, + plates: str, + owner: str, + brand: str, + model: str, + color: str, +) -> None: print("\n\nClient: Adding a car to database.") flyweight = factory.get_flyweight([brand, model, color]) @@ -12,25 +17,25 @@ def add_car_to_police_database(factory: FlyweightFactory, plates: str, flyweight.operation([plates, owner]) -if __name__ == '__main__': +if __name__ == "__main__": """ The client code usually creates a bunch of pre-populated flyweights in the initialization stage of the application. """ - factory = FlyweightFactory([ - ["Chevrolet", "Camaro2018", "pink"], - ["Mercedes Benz", "C300", "black"], - ["Mercedes Benz", "C500", "red"], - ["BMW", "M5", "red"], - ["BMW", "X6", "white"], - ]) + factory = FlyweightFactory( + [ + ["Chevrolet", "Camaro2018", "pink"], + ["Mercedes Benz", "C300", "black"], + ["Mercedes Benz", "C500", "red"], + ["BMW", "M5", "red"], + ["BMW", "X6", "white"], + ] + ) factory.list_flyweights() - add_car_to_police_database( - factory, "CL234IR", "James Doe", "BMW", "M5", "red") - add_car_to_police_database( - factory, "CL234IR", "James Doe", "BMW", "X1", "red") + add_car_to_police_database(factory, "CL234IR", "James Doe", "BMW", "M5", "red") + add_car_to_police_database(factory, "CL234IR", "James Doe", "BMW", "X1", "red") print("\n") diff --git a/structural/proxy/main.py b/structural/proxy/main.py index 424a68f..e57cbbc 100644 --- a/structural/proxy/main.py +++ b/structural/proxy/main.py @@ -15,11 +15,11 @@ def client_code(subject: Subject) -> None: subject.request() -if __name__ == '__main__': +if __name__ == "__main__": print("Client: Executing the client code with a real subject:") real_subject = RealSubject() client_code(real_subject) - print('') + print("") print("Client: Executing the same client code with a proxy:") proxy = Proxy(real_subject) client_code(proxy)