Low-Level Design Patterns: Mastering Abstract Factory and Factory Method for Your Next Tech Interview

Introduction

Welcome back to our series on low-level design patterns for tech interview preparation! In this installment, we’ll explore two closely related creational patterns: the Abstract Factory and Factory Method. By the end of this article, you’ll have a solid understanding of both patterns and be ready to impress your interviewer with your expertise in low-level design. We’ll use Java for our code examples, as it’s a common language in many tech interviews.

Abstract Factory Design Pattern

What is the Abstract Factory Design Pattern?

The Abstract Factory pattern provides an interface for creating families of related or dependent objects without specifying their concrete classes. It’s particularly useful when a system needs to be independent of how its products are created, composed, and represented.

Key Components

  1. Abstract Factory: Declares an interface for operations that create abstract product objects.
  2. Concrete Factory: Implements the operations to create concrete product objects.
  3. Abstract Product: Declares an interface for a type of product object.
  4. Concrete Product: Defines a product object to be created by the corresponding concrete factory.
  5. Client: Uses only interfaces declared by Abstract Factory and Abstract Product classes.

Example: Cross-Platform UI Components

Let’s implement a system for creating UI components that can work across different operating systems.

// Abstract Product A
interface Button {
    void paint();
}

// Abstract Product B
interface Checkbox {
    void paint();
}

// Concrete Products for Windows
class WindowsButton implements Button {
    public void paint() {
        System.out.println("Rendering a button in Windows style");
    }
}

class WindowsCheckbox implements Checkbox {
    public void paint() {
        System.out.println("Rendering a checkbox in Windows style");
    }
}

// Concrete Products for macOS
class MacOSButton implements Button {
    public void paint() {
        System.out.println("Rendering a button in macOS style");
    }
}

class MacOSCheckbox implements Checkbox {
    public void paint() {
        System.out.println("Rendering a checkbox in macOS style");
    }
}

// Abstract Factory
interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

// Concrete Factories
class WindowsFactory implements GUIFactory {
    public Button createButton() {
        return new WindowsButton();
    }
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

class MacOSFactory implements GUIFactory {
    public Button createButton() {
        return new MacOSButton();
    }
    public Checkbox createCheckbox() {
        return new MacOSCheckbox();
    }
}

// Client
class Application {
    private Button button;
    private Checkbox checkbox;

    public Application(GUIFactory factory) {
        button = factory.createButton();
        checkbox = factory.createCheckbox();
    }

    public void paint() {
        button.paint();
        checkbox.paint();
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        GUIFactory factory = new WindowsFactory();
        Application app = new Application(factory);
        app.paint();

        factory = new MacOSFactory();
        app = new Application(factory);
        app.paint();
    }
}

Factory Method Design Pattern

What is the Factory Method Design Pattern?

The Factory Method pattern defines an interface for creating an object, but lets subclasses decide which class to instantiate. It allows a class to defer instantiation to subclasses.

Key Components

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

Example: Document Creator

Let’s implement a system for creating different types of documents.

// Product
interface Document {
    void open();
    void save();
}

// Concrete Products
class PDFDocument implements Document {
    public void open() {
        System.out.println("Opening PDF document");
    }
    public void save() {
        System.out.println("Saving PDF document");
    }
}

class WordDocument implements Document {
    public void open() {
        System.out.println("Opening Word document");
    }
    public void save() {
        System.out.println("Saving Word document");
    }
}

// Creator
abstract class DocumentCreator {
    public abstract Document createDocument();

    public void editDocument() {
        Document doc = createDocument();
        doc.open();
        System.out.println("Editing the document");
        doc.save();
    }
}

// Concrete Creators
class PDFDocumentCreator extends DocumentCreator {
    public Document createDocument() {
        return new PDFDocument();
    }
}

class WordDocumentCreator extends DocumentCreator {
    public Document createDocument() {
        return new WordDocument();
    }
}

// Usage
public class Main {
    public static void main(String[] args) {
        DocumentCreator creator = new PDFDocumentCreator();
        creator.editDocument();

        creator = new WordDocumentCreator();
        creator.editDocument();
    }
}

Key Differences

  1. Scope: Abstract Factory creates families of related objects, while Factory Method creates a single object.
  2. Abstraction Level: Abstract Factory provides a higher level of abstraction, dealing with multiple product types.
  3. Flexibility: Factory Method is more flexible for adding new product types, as it only requires adding a new Concrete Creator.

Interview Tips

When discussing these patterns in a low-level design interview:

  1. Explain the key differences between Abstract Factory and Factory Method.
  2. Highlight how these patterns promote loose coupling between client code and object creation.
  3. Discuss real-world applications, such as UI toolkits, cross-platform development, or plugin architectures.
  4. Be prepared to implement a simple example in Java, like the ones provided above, or adapt them to a domain relevant to the company you’re interviewing with.
  5. Mention potential drawbacks, such as the complexity they can add to the codebase, especially for small applications.
  6. Discuss how these patterns relate to other design principles, such as the Open/Closed Principle and Dependency Inversion Principle.

Conclusion

The Abstract Factory and Factory Method design patterns are powerful tools for creating flexible and maintainable object-oriented systems. By understanding and implementing these patterns, you’ll demonstrate your ability to create extensible and adaptable low-level designs, a crucial skill for any software engineer.

Remember to practice implementing these patterns and be ready to discuss their pros and cons in the context of larger system designs. Good luck with your low-level design interview preparation!

Leave a Reply