Skip to Content
Course content

148: The Abstract Factory Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're an interior designer. Your client wants to furnish a room, but they have a very strict rule: everything must match. If they go with a "Modern" aesthetic, the chair, the sofa, and the coffee table must all be Modern. If they switch to "Victorian," every single piece of furniture needs to be Victorian. You can't just toss a sleek, minimalist glass table into a room full of velvet-tufted mahogany sofas—it would look ridiculous.

In software, we hit this same problem when we have "families" of related objects. You might have a set of UI components (buttons, checkboxes, sliders) that need to look different depending on whether the user is on Windows, macOS, or Linux. If you accidentally mix a Windows-style button with a macOS-style slider, your app looks broken. The Abstract Factory pattern is how we ensure that our code always creates products from the same family without hardcoding specific classes everywhere.

The Problem with Mixing and Matching

You've likely used a Simple Factory before, where one class decides which object to create. That's great for a single product. But when you have groups of products that belong together, a simple factory isn't enough. You'd end up with a massive, messy conditional block trying to keep track of which theme or OS the user is using for every single component you instantiate.

I've seen developers try to solve this by passing a "theme" string into every single constructor. Trust me, that's a nightmare to maintain. Every time you add a new product to the family, you have to update every constructor call in your entire codebase. That's exactly what we're trying to avoid here.

Building the Furniture Ecosystem

Let's map that interior design analogy directly into Java. First, we define the "products" we want to create. We don't care about the specific style yet; we just care that a chair is a chair.

public interface Chair {
    void sitOn();
}

public interface Sofa {
    void lieOn();
}

Now, we create the concrete versions of these products. This is where the "Modern" and "Victorian" specifics live. Notice how we have a separate class for each style of each product.

public class ModernChair implements Chair {
    public void sitOn() { System.out.println("Sitting on a sleek, plastic chair."); }
}

public class VictorianChair implements Chair {
    public void sitOn() { System.out.println("Sitting on a heavy, carved wooden chair."); }
}

public class ModernSofa implements Sofa {
    public void lieOn() { System.out.println("Lying on a grey fabric sofa."); }
}

public class VictorianSofa implements Sofa {
    public void lieOn() { System.out.println("Lying on a velvet tufted sofa."); }
}

Here is the core of the pattern: the Abstract Factory. This isn't a class; it's an interface that declares a set of methods for creating each of the abstract products. It basically says, "Any factory that implements me must be able to produce both a Chair and a Sofa."

public interface FurnitureFactory {
    Chair createChair();
    Sofa createSofa();
}

Finally, we create the Concrete Factories. The Modern factory only makes modern furniture, and the Victorian factory only makes Victorian furniture. This is the "guarantee" that the products will always match.

public class ModernFurnitureFactory implements FurnitureFactory {
    public Chair createChair() { return new ModernChair(); }
    public Sofa createSofa() { return new ModernSofa(); }
}

public class VictorianFurnitureFactory implements FurnitureFactory {
    public Chair createChair() { return new VictorianChair(); }
    public Sofa createSofa() { return new VictorianSofa(); }
}

Putting it to Work in the Client

The beauty of this is that your client code (the part of the app actually using the furniture) doesn't need to know which factory it's using. It just knows it has a FurnitureFactory.

public class Designer {
    private Chair chair;
    private Sofa sofa;

    public Designer(FurnitureFactory factory) {
        // The designer doesn't care if it's Modern or Victorian
        // They just know the factory will give them matching pieces.
        this.chair = factory.createChair();
        this.sofa = factory.createSofa();
    }

    public void showcaseRoom() {
        chair.sitOn();
        sofa.lieOn();
    }
}

To run this, you just pick the factory once at the start of the program. If the client changes their mind and wants Victorian furniture, you change one line of code where the factory is instantiated, and the rest of the application updates automatically. No hunting through your project for "ModernChair" strings.




📋 Practical Task

Implementing a Dark and Light Theme UI Kit

You are building a cross-platform UI library. You need to implement the Abstract Factory pattern to handle two different visual themes: LightMode and DarkMode.

Your task is to create the following structure:

  • Two product interfaces: Button (with a paint() method) and Checkbox (with a paint() method).
  • Four concrete products: LightButton, LightCheckbox, DarkButton, and DarkCheckbox. Each should print a message indicating its theme and type when paint() is called.
  • An abstract factory interface UIFactory that defines createButton() and createCheckbox().
  • Two concrete factories: LightUIFactory and DarkUIFactory.

Finally, create a Application class that takes a UIFactory in its constructor and uses it to create a button and a checkbox. Demonstrate in a main method that you can switch the entire look of the application just by passing a different factory into the Application constructor.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.