In this section, we will explore practical examples of how design patterns can be applied in real-world scenarios. Understanding these examples will help you recognize when and how to use design patterns in your own projects.

Example 1: Singleton Pattern in Database Connections

Problem

In many applications, managing database connections efficiently is crucial. Creating multiple connections can lead to resource exhaustion and performance issues.

Solution

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it. This is particularly useful for managing database connections.

Implementation

class DatabaseConnection:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(DatabaseConnection, cls).__new__(cls)
            # Initialize the database connection here
            cls._instance.connection = cls._initialize_connection()
        return cls._instance

    @staticmethod
    def _initialize_connection():
        # Simulate a database connection initialization
        return "Database Connection Established"

    def get_connection(self):
        return self.connection

# Usage
db1 = DatabaseConnection()
db2 = DatabaseConnection()

print(db1.get_connection())  # Output: Database Connection Established
print(db1 is db2)            # Output: True

Explanation

  • The __new__ method ensures that only one instance of DatabaseConnection is created.
  • The _initialize_connection method simulates the initialization of a database connection.
  • When db1 and db2 are created, they both point to the same instance, ensuring a single database connection.

Exercise

Implement a Singleton pattern for a Logger class that writes logs to a file. Ensure that only one instance of the Logger class is created.

Solution

class Logger:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            cls._instance = super(Logger, cls).__new__(cls)
            cls._instance._initialize_logger()
        return cls._instance

    def _initialize_logger(self):
        self.log_file = open("app.log", "a")

    def log(self, message):
        self.log_file.write(message + "\n")

# Usage
logger1 = Logger()
logger2 = Logger()

logger1.log("This is a log message.")
logger2.log("This is another log message.")

print(logger1 is logger2)  # Output: True

Example 2: Factory Method Pattern in GUI Applications

Problem

Creating different types of GUI components (e.g., buttons, text fields) can lead to code duplication and tight coupling.

Solution

The Factory Method pattern defines an interface for creating an object but allows subclasses to alter the type of objects that will be created.

Implementation

from abc import ABC, abstractmethod

class Button(ABC):
    @abstractmethod
    def render(self):
        pass

class WindowsButton(Button):
    def render(self):
        return "Rendering a Windows button"

class MacOSButton(Button):
    def render(self):
        return "Rendering a MacOS button"

class Dialog(ABC):
    @abstractmethod
    def create_button(self):
        pass

    def render(self):
        button = self.create_button()
        return button.render()

class WindowsDialog(Dialog):
    def create_button(self):
        return WindowsButton()

class MacOSDialog(Dialog):
    def create_button(self):
        return MacOSButton()

# Usage
def client_code(dialog: Dialog):
    print(dialog.render())

client_code(WindowsDialog())  # Output: Rendering a Windows button
client_code(MacOSDialog())    # Output: Rendering a MacOS button

Explanation

  • The Button class is an abstract base class with a render method.
  • WindowsButton and MacOSButton are concrete implementations of the Button class.
  • The Dialog class is an abstract base class with a create_button method.
  • WindowsDialog and MacOSDialog are concrete implementations of the Dialog class, each creating a different type of button.

Exercise

Implement a Factory Method pattern for creating different types of documents (e.g., Word, PDF). Ensure that the correct type of document is created based on the subclass.

Solution

from abc import ABC, abstractmethod

class Document(ABC):
    @abstractmethod
    def open(self):
        pass

class WordDocument(Document):
    def open(self):
        return "Opening a Word document"

class PDFDocument(Document):
    def open(self):
        return "Opening a PDF document"

class Application(ABC):
    @abstractmethod
    def create_document(self):
        pass

    def open_document(self):
        document = self.create_document()
        return document.open()

class WordApplication(Application):
    def create_document(self):
        return WordDocument()

class PDFApplication(Application):
    def create_document(self):
        return PDFDocument()

# Usage
def client_code(application: Application):
    print(application.open_document())

client_code(WordApplication())  # Output: Opening a Word document
client_code(PDFApplication())   # Output: Opening a PDF document

Conclusion

In this section, we explored practical examples of the Singleton and Factory Method patterns. These examples demonstrate how design patterns can be applied to solve common problems in software development. By understanding and practicing these patterns, you can improve the design and maintainability of your applications.

Next, we will delve into more complex patterns and their applications in real projects.

© Copyright 2024. All rights reserved