Introduction

The Factory Method is a creational design pattern that provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created. This pattern promotes loose coupling by eliminating the need to bind application-specific classes into the code.

Key Concepts

  • Factory Method: A method that returns an instance of a class.
  • Product: The object that is being created.
  • Creator: The class that contains the factory method.
  • Concrete Product: The actual instance of the product that is created by the factory method.

Structure

The Factory Method pattern involves the following components:

  1. Product: Defines the interface of objects the factory method creates.
  2. ConcreteProduct: Implements the Product interface.
  3. Creator: Declares the factory method, which returns an object of type Product.
  4. ConcreteCreator: Overrides the factory method to return an instance of ConcreteProduct.

UML Diagram

+----------------+          +----------------+
|    Creator     |          |    Product     |
+----------------+          +----------------+
| + factoryMethod() : Product |<|--+ ConcreteProduct
+----------------+          +----------------+
        ^                           ^
        |                           |
+----------------+          +----------------+
| ConcreteCreator |          | ConcreteProduct |
+----------------+          +----------------+
| + factoryMethod() : Product |
+----------------+

Example

Let's consider a scenario where we need to create different types of documents (e.g., Word, PDF). We can use the Factory Method pattern to achieve this.

Step-by-Step Implementation

  1. Define the Product Interface:
from abc import ABC, abstractmethod

class Document(ABC):
    @abstractmethod
    def open(self):
        pass
  1. Create Concrete Products:
class WordDocument(Document):
    def open(self):
        return "Opening Word document"

class PDFDocument(Document):
    def open(self):
        return "Opening PDF document"
  1. Define the Creator Class:
class Application(ABC):
    @abstractmethod
    def create_document(self) -> Document:
        pass

    def open_document(self):
        doc = self.create_document()
        return doc.open()
  1. Create Concrete Creators:
class WordApplication(Application):
    def create_document(self) -> Document:
        return WordDocument()

class PDFApplication(Application):
    def create_document(self) -> Document:
        return PDFDocument()
  1. Client Code:
def client_code(app: Application):
    print(app.open_document())

if __name__ == "__main__":
    word_app = WordApplication()
    client_code(word_app)

    pdf_app = PDFApplication()
    client_code(pdf_app)

Explanation

  • Document: The abstract base class for all documents.
  • WordDocument and PDFDocument: Concrete implementations of the Document interface.
  • Application: The abstract base class that declares the factory method create_document.
  • WordApplication and PDFApplication: Concrete implementations of the Application class that override the create_document method to return instances of WordDocument and PDFDocument, respectively.

Practical Exercise

Exercise

Create a factory method pattern to handle the creation of different types of notifications (e.g., Email, SMS).

  1. Define an abstract Notification class with a method send.
  2. Create concrete classes EmailNotification and SMSNotification that implement the Notification interface.
  3. Define an abstract NotificationFactory class with a method create_notification.
  4. Create concrete classes EmailNotificationFactory and SMSNotificationFactory that override the create_notification method.
  5. Write client code to demonstrate the usage of the factory method pattern.

Solution

  1. Define the Notification Interface:
from abc import ABC, abstractmethod

class Notification(ABC):
    @abstractmethod
    def send(self):
        pass
  1. Create Concrete Notifications:
class EmailNotification(Notification):
    def send(self):
        return "Sending Email Notification"

class SMSNotification(Notification):
    def send(self):
        return "Sending SMS Notification"
  1. Define the NotificationFactory Class:
class NotificationFactory(ABC):
    @abstractmethod
    def create_notification(self) -> Notification:
        pass

    def notify(self):
        notification = self.create_notification()
        return notification.send()
  1. Create Concrete Factories:
class EmailNotificationFactory(NotificationFactory):
    def create_notification(self) -> Notification:
        return EmailNotification()

class SMSNotificationFactory(NotificationFactory):
    def create_notification(self) -> Notification:
        return SMSNotification()
  1. Client Code:
def client_code(factory: NotificationFactory):
    print(factory.notify())

if __name__ == "__main__":
    email_factory = EmailNotificationFactory()
    client_code(email_factory)

    sms_factory = SMSNotificationFactory()
    client_code(sms_factory)

Common Mistakes and Tips

  • Mistake: Forgetting to override the factory method in the concrete creator classes.

    • Tip: Always ensure that the concrete creator classes provide an implementation for the factory method.
  • Mistake: Creating instances of concrete products directly in the client code.

    • Tip: Use the factory method to create instances to maintain loose coupling.

Conclusion

The Factory Method pattern is a powerful tool for creating objects without specifying the exact class of object that will be created. By using this pattern, you can achieve greater flexibility and maintainability in your code. In the next section, we will explore another creational pattern: the Abstract Factory.

© Copyright 2024. All rights reserved