Programmation Orientée Objet en Python
  • Back to Main Website
  • Home
  • Introduction: Histoire et Concepts
    • Introduction: Histoire et Concepts
    • Histoire de la programmation
    • Première Structuration des données
    • Naissance de la POO
    • Python: tout n’est qu’objet
    • Python : Simplicité des objets et performance sous-jacente
    • Classes en Python : Concepts fondamentaux

    • Travaux Pratiques
    • Construire sa propre Liste
    • Construire son propre DataFrame
  • Encapsulation, Héritage, Composition et Dunder
    • Encapsulation, Heritage, Composition et Dunder
    • Encapsulation en Python
    • Héritage en Python : Concept et intérêt
    • Héritage vs Composition
    • Méthodes Dunder en Python
    • Python call Method: A Fun Exploration

    • Travaux Pratiques
    • TP: Heritage avec le pricing d’option
    • TP : Ajouter des méthodes dunder à DataFrameSimple
    • TP : Étendre la classe Liste avec des méthodes dunder
    • TP: Dunder Method with Tensor for Automatic Differentiation
  • Polymorphisme et Surcharge
    • Polymorphisme et Surcharge
    • Polymorphism in Object-Oriented Programming
    • Polymorphism in Python: Function Overloading and Type Checking
    • Class Creation: Standard vs type()
    • Type Hinting, Typing Module, and Linters in Python
    • Abstract Classes
    • Protocol Classes

    • Travaux Pratiques
    • TP
  • Decorators
    • Design Patterns
    • The decorator pattern
    • Decorator Practically
    • Built-in Decorators and Standard Library Decorators in Python
    • Practical Decorators in Python Libraries

    • Travaux Pratiques
    • TP: Monte Carlo Option Pricing with Decorators
    • TP: Optimizing Heston Model Monte Carlo Simulation
  • Project Management and Packaging
    • Project and Package
    • Organizing Python Projects
    • Understanding imports
    • Python Package Management and Virtual Environments
    • Unit Testing in Python

    • Travaux Pratiques
    • TP: Creating a Linear Regression Package
  • Design Patterns
    • OOP Design Patterns
    • Python-Specific Design Patterns
    • Creation Design Patterns
    • Structural Design Patterns
    • Behavioral Design Pattern

    • Travaux Pratiques
    • TP
  • Sujets de Projets possibles
    • Projets
    • Projets POO - 2024-2025
  • Code source
  1. Creation Design Patterns
  • OOP Design Patterns
  • Python-Specific Design Patterns
  • Creation Design Patterns
  • Structural Design Patterns
  • Behavioral Design Pattern
  • Travaux Pratiques
    • TP

On this page

  • Creational Design Patterns in Python
    • 1. Singleton Pattern
      • 1.1 Implementation using __new__
      • 1.2 Implementation using a Decorator
      • 1.3 Pros and Cons
    • 2. Factory Method Pattern
      • 2.1 Example: Matrix Computation with Different Backends
      • 2.2 Benefits of Factory Method
    • 3. Abstract Factory Pattern
      • 3.1 Example: Cross-platform UI Components
      • 3.2 Benefits of Abstract Factory
    • 4. Builder Pattern
      • 4.1 Example: Custom Computer Builder
      • 4.2 Benefits of Builder
    • Conclusion

Creation Design Patterns

Cours
Fondamentaux
Learn creational design pattern
Author

Remi Genet

Published

2024-10-21

Pyodide Status

Initializing Python Packages

Creational Design Patterns in Python


Creational design patterns provide various object creation mechanisms, which increase flexibility and reuse of existing code.

1. Singleton Pattern

The Singleton pattern ensures a class has only one instance and provides a global point of access to it.

1.1 Implementation using __new__

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Singleton:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    def some_business_logic(self):
        # ...
# Usage
s1 = Singleton()
s2 = Singleton()
print(s1 is s2)  # Output: True

1.2 Implementation using a Decorator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def singleton(class_):
    instances = {}
    def get_instance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]
    return get_instance
@singleton
class DatabaseConnection:
    def __init__(self):
        # Initialize the database connection
# Usage
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(db1 is db2)  # Output: True

1.3 Pros and Cons

  • Pros:
    • Ensures a single instance
    • Global access point
    • Lazy initialization
  • Cons:
    • Violates Single Responsibility Principle
    • Can make unit testing difficult
    • Can hide bad design

2. Factory Method Pattern

The Factory Method pattern provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

2.1 Example: Matrix Computation with Different Backends

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import os
from abc import ABC, abstractmethod
from typing import List, Union
class Matrix(ABC):
    @abstractmethod
    def __init__(self, data: Union[List[List[float]], List[float]]):
        pass
    @abstractmethod
    def multiply(self, other: 'Matrix') -> 'Matrix':
        pass
    @abstractmethod
    def add(self, other: 'Matrix') -> 'Matrix':
        pass
class MatrixFactory(ABC):
    @abstractmethod
    def create_matrix(self, data: Union[List[List[float]], List[float]]) -> Matrix:
        pass
class PythonMatrix(Matrix):
    # Implementation for pure Python
class PythonMatrixFactory(MatrixFactory):
    def create_matrix(self, data):
        return PythonMatrix(data)
# Similar classes for NumPy and JAX implementations
def get_matrix_factory() -> MatrixFactory:
    backend = os.environ.get('MATRIX_BACKEND', 'PYTHON').upper()
    if backend == 'NUMPY' and NumPyMatrixFactory is not None:
        return NumPyMatrixFactory()
    elif backend == 'JAX' and JAXMatrixFactory is not None:
        return JAXMatrixFactory()
    else:
        return PythonMatrixFactory()
# Usage
factory = get_matrix_factory()
mat1 = factory.create_matrix([[1, 2], [3, 4]])
mat2 = factory.create_matrix([[5, 6], [7, 8]])
result = mat1.multiply(mat2)

2.2 Benefits of Factory Method

  • Provides flexibility in object creation
  • Promotes loose coupling
  • Adheres to the Open/Closed Principle
  • Simplifies adding new types of objects

3. Abstract Factory Pattern

The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes.

3.1 Example: Cross-platform UI Components

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from abc import ABC, abstractmethod
# Abstract Product Classes
class Button(ABC):
    @abstractmethod
    def paint(self):
        pass
class Checkbox(ABC):
    @abstractmethod
    def paint(self):
        pass
# Concrete Product Classes
class WindowsButton(Button):
    def paint(self):
        return "Render a button in Windows style"
class MacOSButton(Button):
    def paint(self):
        return "Render a button in MacOS style"
class WindowsCheckbox(Checkbox):
    def paint(self):
        return "Render a checkbox in Windows style"
class MacOSCheckbox(Checkbox):
    def paint(self):
        return "Render a checkbox in MacOS style"
# Abstract Factory
class GUIFactory(ABC):
    @abstractmethod
    def create_button(self) -> Button:
        pass
    @abstractmethod
    def create_checkbox(self) -> Checkbox:
        pass
# Concrete Factories
class WindowsFactory(GUIFactory):
    def create_button(self) -> Button:
        return WindowsButton()
    def create_checkbox(self) -> Checkbox:
        return WindowsCheckbox()
class MacOSFactory(GUIFactory):
    def create_button(self) -> Button:
        return MacOSButton()
    def create_checkbox(self) -> Checkbox:
        return MacOSCheckbox()
# Client Code
def create_ui(factory: GUIFactory):
    button = factory.create_button()
    checkbox = factory.create_checkbox()
    return button.paint() + " and " + checkbox.paint()
# Usage
windows_ui = create_ui(WindowsFactory())
macos_ui = create_ui(MacOSFactory())
print(windows_ui)
print(macos_ui)

3.2 Benefits of Abstract Factory

  • Ensures compatibility between created objects
  • Isolates concrete classes
  • Simplifies exchanging product families
  • Promotes consistency among products

4. Builder Pattern

The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations.

4.1 Example: Custom Computer Builder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class Computer:
    def __init__(self):
        self.parts = []
    def add(self, part):
        self.parts.append(part)
    def list_parts(self):
        return f"Computer parts: {', '.join(self.parts)}"
class ComputerBuilder(ABC):
    @abstractmethod
    def add_cpu(self):
        pass
    @abstractmethod
    def add_memory(self):
        pass
    @abstractmethod
    def add_storage(self):
        pass
    @abstractmethod
    def get_result(self) -> Computer:
        pass
class GamingComputerBuilder(ComputerBuilder):
    def __init__(self):
        self.computer = Computer()
    def add_cpu(self):
        self.computer.add("High-end CPU")
    def add_memory(self):
        self.computer.add("32GB RAM")
    def add_storage(self):
        self.computer.add("1TB SSD")
    def get_result(self) -> Computer:
        return self.computer
class OfficeComputerBuilder(ComputerBuilder):
    def __init__(self):
        self.computer = Computer()
    def add_cpu(self):
        self.computer.add("Mid-range CPU")
    def add_memory(self):
        self.computer.add("16GB RAM")
    def add_storage(self):
        self.computer.add("512GB SSD")
    def get_result(self) -> Computer:
        return self.computer
class Director:
    def __init__(self, builder: ComputerBuilder):
        self.builder = builder
    def construct_computer(self):
        self.builder.add_cpu()
        self.builder.add_memory()
        self.builder.add_storage()
# Usage
gaming_builder = GamingComputerBuilder()
director = Director(gaming_builder)
director.construct_computer()
gaming_pc = gaming_builder.get_result()
print(gaming_pc.list_parts())
office_builder = OfficeComputerBuilder()
director = Director(office_builder)
director.construct_computer()
office_pc = office_builder.get_result()
print(office_pc.list_parts())

4.2 Benefits of Builder

  • Allows step-by-step creation of complex objects
  • Can use the same construction code for different representations
  • Isolates complex construction code from business logic

Conclusion

Creational design patterns solve problems related to object creation in software design. They provide flexibility, improve code reusability, and help manage complexity in object-oriented systems. Each pattern has its specific use cases, and understanding them allows developers to choose the right tool for the job when designing software systems.

Back to top
Python-Specific Design Patterns
Structural Design Patterns

Programmation Orienté Object en Python, Rémi Genet.
Licence
Code source disponible sur Github

 

Site construit avec et Quarto
Inspiration pour la mise en forme du site ici
Code source disponible sur GitHub