Abstract Factory


Picture pairing a VLC audio player with a Windows video player because two separate factory calls got made in two different places and nobody enforced they came from the same platform. Individually each call is correct, together they’re a mismatched pair that behaves badly in ways that are annoying to trace back to “these two objects were never supposed to be used together.” Abstract Factory is what stops that pairing from being possible in the first place.

The problem

Sometimes creating one object isn’t the problem, creating a consistent family of objects is. If you pick “Windows” as your platform, every related object you construct after that, audio player, video player, whatever else belongs to that family, needs to actually be the Windows variant. Two separate Factory Method calls can’t guarantee that, there’s nothing stopping you from calling one factory for audio and a different one for video.

Without the pattern

Before MediaFactory exists, the obvious move is to skip factories entirely and just instantiate what you need at the call site, new WindowsAudioPlayer() in one place, new WindowsVideoPlayer() in another, wherever the codebase happens to need a player. It works, and it keeps working right up until someone adds a settings screen that lets the user pick VLC instead of Windows. Now every one of those new WindowsXPlayer() calls has to be found and swapped for the VLC equivalent, and there’s nothing tying them together, no compiler error, no test failure, if you catch nine of the ten call sites and miss the tenth.

flowchart TD
    A[User flips platform setting: Windows to VLC] --> B[SettingsPanel.java updated:<br/>new WindowsAudioPlayer becomes new VlcAudioPlayer]
    A --> C[PlaybackController.java missed:<br/>still hardcodes new WindowsVideoPlayer]
    B --> D[App running]
    C --> D
    D --> E[VlcAudioPlayer paired with WindowsVideoPlayer<br/>in the same playback session]
    E --> F[Audio and video assume different platform conventions,<br/>stream desyncs, and the bug report says nothing<br/>about 'someone forgot a call site']

Nothing in the naive version stops that pairing. AudioPlayer and VideoPlayer are constructed independently, so the fact that they’re supposed to travel together as a “Windows family” or a “VLC family” only exists in the programmer’s head, not in the code.

With the pattern

There are two abstract products here, AudioPlayer (playSong()) and VideoPlayer (playVideo()), each with a Windows implementation and a VLC implementation, WindowsAudioPlayer/WindowsVideoPlayer and VlcAudioPlayer/VlcVideoPlayer.

The abstract class MediaFactory declares two creation methods, createAudioPlayer() and createVideoPlayer(), and doesn’t implement either. WindowsMediaFactory extends it and implements both to return the Windows pair, VlcMediaFactory does the same for the VLC pair. That’s the actual guarantee the pattern buys you: once you’re holding a WindowsMediaFactory, every product it hands you is a Windows product, there is no code path where WindowsMediaFactory.createAudioPlayer() returns anything VLC-flavored.

MediaFactoryProducer.getFactory(String factoryType) sits one level above that, it’s a factory that returns a factory, same normalize-then-switch shape as MediaPlayerFactory, same null and unknown-type guards throwing IllegalArgumentException. That’s the layer client code actually talks to, pick a platform once, get back a MediaFactory, pull every related product through that one object.

classDiagram
    class AudioPlayer {
        <<interface>>
        +playSong() void
    }
    class VideoPlayer {
        <<interface>>
        +playVideo() void
    }
    class WindowsAudioPlayer
    class WindowsVideoPlayer
    class VlcAudioPlayer
    class VlcVideoPlayer
    class MediaFactory {
        <<abstract>>
        +createAudioPlayer() AudioPlayer
        +createVideoPlayer() VideoPlayer
    }
    class WindowsMediaFactory {
        +createAudioPlayer() AudioPlayer
        +createVideoPlayer() VideoPlayer
    }
    class VlcMediaFactory {
        +createAudioPlayer() AudioPlayer
        +createVideoPlayer() VideoPlayer
    }
    class MediaFactoryProducer {
        +WINDOWS_FACTORY: String
        +VLC_FACTORY: String
        +static getFactory(factoryType: String) MediaFactory
    }
    AudioPlayer <|.. WindowsAudioPlayer
    AudioPlayer <|.. VlcAudioPlayer
    VideoPlayer <|.. WindowsVideoPlayer
    VideoPlayer <|.. VlcVideoPlayer
    MediaFactory <|-- WindowsMediaFactory
    MediaFactory <|-- VlcMediaFactory
    WindowsMediaFactory ..> WindowsAudioPlayer : creates
    WindowsMediaFactory ..> WindowsVideoPlayer : creates
    VlcMediaFactory ..> VlcAudioPlayer : creates
    VlcMediaFactory ..> VlcVideoPlayer : creates
    MediaFactoryProducer ..> MediaFactory : creates

What it costs you

Four extra types, MediaFactory, WindowsMediaFactory, VlcMediaFactory, MediaFactoryProducer, to create two kinds of objects that used to just be a plain constructor call. Tracing which concrete class actually gets built now takes two hops instead of one: getFactory("WINDOWS") hands you back a MediaFactory, and only when you go read WindowsMediaFactory.createAudioPlayer() do you find the WindowsAudioPlayer construction. Grepping for new WindowsAudioPlayer at the call site that actually uses one won’t find it, there isn’t one there, it’s buried inside the factory. And the family guarantee cuts both ways: add a third product, say SubtitleRenderer, and MediaFactory needs a new abstract method, and WindowsMediaFactory and VlcMediaFactory both need it implemented, whether or not either one has any other reason to change that day.

When to reach for it

Reach for it when the products genuinely need to travel together, cross-platform UI toolkits (buttons and checkboxes that all need to be the same theme), driver families, anything where picking “provider A” for one piece of the family and “provider B” for another would be a bug, not a feature. If your factory only ever creates one kind of product, you don’t need this, plain Factory Method covers it.

The takeaway

Abstract Factory buys you a guarantee that Factory Method alone can’t, that everything created through one factory instance belongs to the same family. If nothing in your system actually requires that consistency, you’re paying extra structure for nothing.

Read the full source on GitHub.

← Back to Creational Patterns

Want to get blog posts over email?

Enter your email address and get notified when there's a new post!

Comments