mirror of
https://github.com/rjNemo/design-patterns
synced 2026-06-06 02:26:40 +00:00
parent
d5e94c90a1
commit
7b0e4a5801
18 changed files with 196 additions and 40 deletions
|
|
@ -9,3 +9,4 @@
|
||||||
- [Abstract Factory](creational/abstract-factory/README.md)
|
- [Abstract Factory](creational/abstract-factory/README.md)
|
||||||
- [Builder](creational/builder/README.md)
|
- [Builder](creational/builder/README.md)
|
||||||
- [Prototype](creational/prototype/README.md)
|
- [Prototype](creational/prototype/README.md)
|
||||||
|
- [Singleton](creational/singleton/README.md)
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,4 @@
|
||||||
- [Abstract Factory](abstract-factory/README.md)
|
- [Abstract Factory](abstract-factory/README.md)
|
||||||
- [Builder](builder/README.md)
|
- [Builder](builder/README.md)
|
||||||
- [Prototype](prototype/README.md)
|
- [Prototype](prototype/README.md)
|
||||||
|
- [Singleton](singleton/README.md)
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ class AbstractFactory(ABC):
|
||||||
"""
|
"""
|
||||||
The Abstract Factory interface declares a set of methods that return
|
The Abstract Factory interface declares a set of methods that return
|
||||||
different abstract products. These products are called a family and are
|
different abstract products. These products are called a family and are
|
||||||
related by a high-level theme or concept. Products of one family are usually
|
related by a high-level theme or concept. Products of one family are
|
||||||
able to collaborate among themselves. A family of products may have several
|
usually able to collaborate among themselves. A family of products may have
|
||||||
variants, but the products of one variant are incompatible with products of
|
several variants, but the products of one variant are incompatible with
|
||||||
another.
|
products of another.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC
|
||||||
|
|
||||||
|
|
||||||
class AbstractProductA(ABC):
|
class AbstractProductA(ABC):
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ from AbstractProductA import AbstractProductA
|
||||||
class AbstractProductB(ABC):
|
class AbstractProductB(ABC):
|
||||||
"""
|
"""
|
||||||
Here's the the base interface of another product. All products can interact
|
Here's the the base interface of another product. All products can interact
|
||||||
with each other, but proper interaction is possible only between products of
|
with each other, but proper interaction is possible only between products
|
||||||
the same concrete variant.
|
of the same concrete variant.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
from AbstractFactory import AbstractFactory
|
from AbstractFactory import AbstractFactory
|
||||||
from products import (ConcreteProductA1, ConcreteProductA2, ConcreteProductB1,
|
from products import (
|
||||||
ConcreteProductB2)
|
ConcreteProductA1,
|
||||||
|
ConcreteProductA2,
|
||||||
|
ConcreteProductB1,
|
||||||
|
ConcreteProductB2,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ConcreteFactory1(AbstractFactory):
|
class ConcreteFactory1(AbstractFactory):
|
||||||
"""
|
"""
|
||||||
Concrete Factories produce a family of products that belong to a single
|
Concrete Factories produce a family of products that belong to a single
|
||||||
variant. The factory guarantees that resulting products are compatible. Note
|
variant. The factory guarantees that resulting products are compatible.
|
||||||
that signatures of the Concrete Factory's methods return an abstract
|
Note that signatures of the Concrete Factory's methods return an abstract
|
||||||
product, while inside the method a concrete product is instantiated.
|
product, while inside the method a concrete product is instantiated.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,15 +15,14 @@ class ConcreteProductA2(AbstractProductA):
|
||||||
|
|
||||||
|
|
||||||
class ConcreteProductB1(AbstractProductB):
|
class ConcreteProductB1(AbstractProductB):
|
||||||
|
|
||||||
def useful_function_b(self) -> str:
|
def useful_function_b(self) -> str:
|
||||||
return "The result of the product B1."
|
return "The result of the product B1."
|
||||||
|
|
||||||
def another_useful_function_b(self, collaborator: AbstractProductA) -> str:
|
def another_useful_function_b(self, collaborator: AbstractProductA) -> str:
|
||||||
"""
|
"""
|
||||||
The variant, Product B1, is only able to work correctly with the variant,
|
The variant, Product B1, is only able to work correctly with the
|
||||||
Product A1. Nevertheless, it accepts any instance of AbstractProductA as an
|
variant, Product A1. Nevertheless, it accepts any instance of
|
||||||
argument.
|
AbstractProductA as an argument.
|
||||||
"""
|
"""
|
||||||
result = collaborator.useful_function_a()
|
result = collaborator.useful_function_a()
|
||||||
return f"The result of the B1 collaborating with the ({result})"
|
return f"The result of the B1 collaborating with the ({result})"
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,9 @@ class Director:
|
||||||
@builder.setter
|
@builder.setter
|
||||||
def builder(self, builder: Builder) -> None:
|
def builder(self, builder: Builder) -> None:
|
||||||
"""
|
"""
|
||||||
The Director works with any builder instance that the client code passes
|
The Director works with any builder instance that the client code
|
||||||
to it. This way, the client code may alter the final type of the newly
|
passes to it. This way, the client code may alter the final type of the
|
||||||
assembled product.
|
newly assembled product.
|
||||||
"""
|
"""
|
||||||
self._builder = builder
|
self._builder = builder
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ class ConcreteBuilder1(Builder):
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
A fresh builder instance should contain a blank product object, which is
|
A fresh builder instance should contain a blank product object, which
|
||||||
used in further assembly.
|
is used in further assembly.
|
||||||
"""
|
"""
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
|
|
@ -25,8 +25,8 @@ class ConcreteBuilder1(Builder):
|
||||||
Concrete Builders are supposed to provide their own methods for
|
Concrete Builders are supposed to provide their own methods for
|
||||||
retrieving results. That's because various types of builders may create
|
retrieving results. That's because various types of builders may create
|
||||||
entirely different products that don't follow the same interface.
|
entirely different products that don't follow the same interface.
|
||||||
Therefore, such methods cannot be declared in the base Builder interface
|
Therefore, such methods cannot be declared in the base Builder
|
||||||
(at least in a statically typed programming language).
|
interface (at least in a statically typed programming language).
|
||||||
|
|
||||||
Usually, after returning the end result to the client, a builder
|
Usually, after returning the end result to the client, a builder
|
||||||
instance is expected to be ready to start producing another product.
|
instance is expected to be ready to start producing another product.
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ class Product1:
|
||||||
It makes sense to use the Builder pattern only when your products are quite
|
It makes sense to use the Builder pattern only when your products are quite
|
||||||
complex and require extensive configuration.
|
complex and require extensive configuration.
|
||||||
|
|
||||||
Unlike in other creational patterns, different concrete builders can produce
|
Unlike in other creational patterns, different concrete builders can
|
||||||
unrelated products. In other words, results of various builders may not
|
produce unrelated products. In other words, results of various builders may
|
||||||
always follow the same interface.
|
not always follow the same interface.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ class ICreator(ABC):
|
||||||
product = self.factory_method()
|
product = self.factory_method()
|
||||||
|
|
||||||
# Now, use the product.
|
# Now, use the product.
|
||||||
result = f"Creator: The same creator's code has just worked with {product.operation()}"
|
result = f"""
|
||||||
|
Creator: The same creator's code has just worked with
|
||||||
|
{product.operation()}
|
||||||
|
"""
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,5 @@ class ConcreteCreator1(ICreator):
|
||||||
|
|
||||||
|
|
||||||
class ConcreteCreator2(ICreator):
|
class ConcreteCreator2(ICreator):
|
||||||
|
|
||||||
def factory_method(self) -> ConcreteProduct2:
|
def factory_method(self) -> ConcreteProduct2:
|
||||||
return ConcreteProduct2()
|
return ConcreteProduct2()
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,16 @@ from ICreator import ICreator
|
||||||
|
|
||||||
def client_code(creator: ICreator) -> None:
|
def client_code(creator: ICreator) -> None:
|
||||||
"""
|
"""
|
||||||
The client code works with an instance of a concrete creator, albeit through
|
The client code works with an instance of a concrete creator, albeit
|
||||||
its base interface. As long as the client keeps working with the creator via
|
through its base interface. As long as the client keeps working with the
|
||||||
the base interface, you can pass it any creator's subclass.
|
creator via the base interface, you can pass it any creator's subclass.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print(f"Client: I'm not aware of the creator's class, but it still works.\n"
|
print(
|
||||||
f"{creator.some_operation()}", end="")
|
f"Client: I'm not aware of the creator's class, but it still works.\n"
|
||||||
|
f"{creator.some_operation()}",
|
||||||
|
end="",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
print("App: Launched with the ConcreteCreator1.")
|
print("App: Launched with the ConcreteCreator1.")
|
||||||
|
|
|
||||||
|
|
@ -21,15 +21,16 @@ class SomeComponent:
|
||||||
implementations have to override `__copy__` and `__deepcopy__` member
|
implementations have to override `__copy__` and `__deepcopy__` member
|
||||||
functions.
|
functions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
some_int: int
|
some_int: int
|
||||||
some_list_of_objects: List
|
some_list_of_objects: List
|
||||||
some_circular_ref: Any
|
some_circular_ref: Any
|
||||||
|
|
||||||
def __copy__(self) -> SomeComponent:
|
def __copy__(self) -> SomeComponent:
|
||||||
"""
|
"""
|
||||||
Create a shallow copy. This method will be called whenever someone calls
|
Create a shallow copy. This method will be called whenever someone
|
||||||
`copy.copy` with this object and the returned value is returned as the
|
calls `copy.copy` with this object and the returned value is returned
|
||||||
new shallow copy.
|
as the new shallow copy.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# First, let's create copies of the nested objects.
|
# First, let's create copies of the nested objects.
|
||||||
|
|
@ -38,8 +39,7 @@ class SomeComponent:
|
||||||
|
|
||||||
# Then, let's clone the object itself, using the prepared clones of the
|
# Then, let's clone the object itself, using the prepared clones of the
|
||||||
# nested objects.
|
# nested objects.
|
||||||
new = self.__class__(
|
new = self.__class__(self.some_int, some_list_of_objects, some_circular_ref)
|
||||||
self.some_int, some_list_of_objects, some_circular_ref)
|
|
||||||
new.__dict__.update(self.__dict__)
|
new.__dict__.update(self.__dict__)
|
||||||
|
|
||||||
return new
|
return new
|
||||||
|
|
@ -63,8 +63,7 @@ class SomeComponent:
|
||||||
|
|
||||||
# Then, let's clone the object itself, using the prepared clones of the
|
# Then, let's clone the object itself, using the prepared clones of the
|
||||||
# nested objects.
|
# nested objects.
|
||||||
new = self.__class__(
|
new = self.__class__(self.some_int, some_list_of_objects, some_circular_ref)
|
||||||
self.some_int, some_list_of_objects, some_circular_ref)
|
|
||||||
|
|
||||||
new.__dict__ = copy.deepcopy(self.__dict__, memo)
|
new.__dict__ = copy.deepcopy(self.__dict__, memo)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ circular_ref.set_parent(component)
|
||||||
|
|
||||||
shallow_copied_component = copy.copy(component)
|
shallow_copied_component = copy.copy(component)
|
||||||
|
|
||||||
# Let's change the list in shallow_copied_component and see if it changes in component.
|
# Let's change the list in shallow_copied_component and see if it changes in
|
||||||
|
# component.
|
||||||
shallow_copied_component.some_list_of_objects.append("another object")
|
shallow_copied_component.some_list_of_objects.append("another object")
|
||||||
if component.some_list_of_objects[-1] == "another object":
|
if component.some_list_of_objects[-1] == "another object":
|
||||||
print(
|
print(
|
||||||
|
|
|
||||||
39
creational/singleton/README.md
Normal file
39
creational/singleton/README.md
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Singleton
|
||||||
|
|
||||||
|
Singleton is a creational design pattern, which ensures that only one object of its kind exists and provides a single point of access to it for any other code.
|
||||||
|
|
||||||
|
Singleton has almost the same pros and cons as global variables. Although they’re super-handy, they break the modularity of your code.
|
||||||
|
|
||||||
|
You can’t just use a class that depends on Singleton in some other context. You’ll have to carry the Singleton class as well. Most of the time, this limitation comes up during the creation of unit tests.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Singleton is a creational design pattern that lets you ensure that a class has only one instance, while providing a global access point to this instance.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The Singleton pattern solves two problems at the same time, violating the Single Responsibility Principle:
|
||||||
|
|
||||||
|
1. Ensure that a class has just a single instance.
|
||||||
|
|
||||||
|
2. Provide a global access point to that instance.
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
All implementations of the Singleton have these two steps in common:
|
||||||
|
|
||||||
|
- Make the default constructor private, to prevent other objects from using the new operator with the Singleton class.
|
||||||
|
- Create a static creation method that acts as a constructor. Under the hood, this method calls the private constructor to create an object and saves it in a static field. All following calls to this method return the cached object.
|
||||||
|
If your code has access to the Singleton class, then it’s able to call the Singleton’s static method. So whenever that method is called, the same object is always returned.
|
||||||
|
|
||||||
|
## How to Implement
|
||||||
|
|
||||||
|
1. Add a private static field to the class for storing the singleton instance.
|
||||||
|
|
||||||
|
1. Declare a public static creation method for getting the singleton instance.
|
||||||
|
|
||||||
|
1. Implement “lazy initialization” inside the static method. It should create a new object on its first call and put it into the static field. The method should always return that instance on all subsequent calls.
|
||||||
|
|
||||||
|
1. Make the constructor of the class private. The static method of the class will still be able to call the constructor, but not the other objects.
|
||||||
|
|
||||||
|
1. Go over the client code and replace all direct calls to the singleton’s constructor with calls to its static creation method.
|
||||||
38
creational/singleton/naive.py
Normal file
38
creational/singleton/naive.py
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
class SingletonMeta(type):
|
||||||
|
"""
|
||||||
|
The Singleton class can be implemented in different ways in Python. Some
|
||||||
|
possible methods include: base class, decorator, metaclass. We will use the
|
||||||
|
metaclass because it is best suited for this purpose.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_instances = {}
|
||||||
|
|
||||||
|
def __call__(cls, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Possible changes to the value of the `__init__` argument do not affect
|
||||||
|
the returned instance.
|
||||||
|
"""
|
||||||
|
if cls not in cls._instances:
|
||||||
|
instance = super().__call__(*args, **kwargs)
|
||||||
|
cls._instances[cls] = instance
|
||||||
|
return cls._instances[cls]
|
||||||
|
|
||||||
|
|
||||||
|
class Singleton(metaclass=SingletonMeta):
|
||||||
|
def some_business_logic(self):
|
||||||
|
"""
|
||||||
|
Finally, any singleton should define some business logic, which can be
|
||||||
|
executed on its instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
s1 = Singleton()
|
||||||
|
s2 = Singleton()
|
||||||
|
|
||||||
|
if id(s1) == id(s2):
|
||||||
|
print("Singleton works, both variables contain the same instance.")
|
||||||
|
else:
|
||||||
|
print("Singleton failed, variables contain different instances.")
|
||||||
69
creational/singleton/thread_safe.py
Normal file
69
creational/singleton/thread_safe.py
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
from threading import Lock, Thread
|
||||||
|
|
||||||
|
|
||||||
|
class SingletonMeta(type):
|
||||||
|
"""
|
||||||
|
This is a thread-safe implementation of Singleton.
|
||||||
|
"""
|
||||||
|
|
||||||
|
_instances = {}
|
||||||
|
|
||||||
|
# We now have a lock object that will be used to synchronize threads during
|
||||||
|
# first access to the Singleton.
|
||||||
|
_lock = Lock()
|
||||||
|
|
||||||
|
def __call__(cls, *args, **kwargs):
|
||||||
|
"""
|
||||||
|
Possible changes to the value of the `__init__` argument do not affect
|
||||||
|
the returned instance.
|
||||||
|
"""
|
||||||
|
# Now, imagine that the program has just been launched. Since there's
|
||||||
|
# no Singleton instance yet, multiple threads can simultaneously pass
|
||||||
|
# the previous conditional and reach this point almost at the same time
|
||||||
|
# The first of them will acquire lock and will proceed further, while
|
||||||
|
# the rest will wait here.
|
||||||
|
with cls._lock:
|
||||||
|
# The first thread to acquire the lock, reaches this conditional,
|
||||||
|
# goes inside and creates the Singleton instance. Once it leaves
|
||||||
|
# the lock block, a thread that might have been waiting for the
|
||||||
|
# lock release may then enter this section. But since the Singleton
|
||||||
|
# field is already initialized the thread won't create a new object
|
||||||
|
if cls not in cls._instances:
|
||||||
|
instance = super().__call__(*args, **kwargs)
|
||||||
|
cls._instances[cls] = instance
|
||||||
|
return cls._instances[cls]
|
||||||
|
|
||||||
|
|
||||||
|
class Singleton(metaclass=SingletonMeta):
|
||||||
|
value: str = None
|
||||||
|
"""
|
||||||
|
We'll use this property to prove that our Singleton really works.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, value: str) -> None:
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def some_business_logic(self):
|
||||||
|
"""
|
||||||
|
Finally, any singleton should define some business logic, which can be
|
||||||
|
executed on its instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def test_singleton(value: str) -> None:
|
||||||
|
singleton = Singleton(value)
|
||||||
|
print(singleton.value)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(
|
||||||
|
"If you see the same value, then singleton was reused (yay!)\n"
|
||||||
|
"If you see different values, "
|
||||||
|
"then 2 singletons were created (booo!!)\n\n"
|
||||||
|
"RESULT:\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
process1 = Thread(target=test_singleton, args=("FOO",))
|
||||||
|
process2 = Thread(target=test_singleton, args=("BAR",))
|
||||||
|
process1.start()
|
||||||
|
process2.start()
|
||||||
Loading…
Reference in a new issue