Sketch a small Abstract Factory in Java for a family of related products. What are the participants and why is the client decoupled?
answer
- Four participants: abstract product, concrete product, abstract factory, concrete factory
- One creation method per product type on the factory
- Client holds abstractions only — no `new` on concretes
- Factory injected from outside (config/lookup/DI)
- New variant = cheap; new product type = touch every factory
basics
~20 sDefine abstract product interfaces, an abstract factory interface with a create method per product, and concrete factories that return one matching set of products. The client holds the abstract factory and calls its methods, never using new on concretes.
solid answer
~40 sAn Abstract Factory has four kinds of participants: abstract products (interfaces for each product type), concrete products (implementations grouped by variant), the abstract factory (an interface/abstract class with one creation method per product), and concrete factories (each returning one consistent family). The client receives an abstract factory by some external means — configuration, a lookup, or dependency injection — and calls its creation methods, holding only abstract-product references. Because the client never writes `new ConcreteProduct()`, swapping the entire family is a single substitution of the factory; the rest of the code is untouched and can't accidentally mix products from different variants. That's the decoupling and the consistency guarantee. In the JDK, DocumentBuilderFactory.newInstance() supplies the concrete factory and newDocumentBuilder() is the creation method; your code mirrors that shape.
code
java · 27 linesinterface Button { void render(); }
interface Checkbox { void render(); }
// Abstract factory: one creation method per product type
interface ThemeFactory {
Button createButton();
Checkbox createCheckbox();
}
class DarkThemeFactory implements ThemeFactory {
public Button createButton() { return new DarkButton(); }
public Checkbox createCheckbox() { return new DarkCheckbox(); }
}
// Client depends only on the abstractions; the family is injected.
class Toolbar {
private final Button button;
private final Checkbox checkbox;
Toolbar(ThemeFactory factory) {
this.button = factory.createButton(); // never `new DarkButton()`
this.checkbox = factory.createCheckbox();
}
void render() { button.render(); checkbox.render(); }
}
class DarkButton implements Button { public void render() {} }
class DarkCheckbox implements Checkbox { public void render() {} }go deeper
Can list the participants and write a simple factory interface with two create methods, and knows the client uses interfaces, not new.
Implements a correct two-variant family, explains why injecting the factory decouples the client, and maps the participants onto DocumentBuilderFactory.
Discusses the consistency invariant, the add-variant-vs-add-product asymmetry, testability via a fake factory, and when a simpler pattern suffices.
Frames the choice against DI containers and the Open/Closed Principle, weighs interface-growth cost across a large product family, and considers configuration/lookup strategy for selecting the variant at scale.
## Building one from scratch Let's model a GUI toolkit family (a textbook scenario) with two variants — say a `Dark` and a `Light` theme — each producing a matching `Button` and `Checkbox`. ### The four participants 1. **Abstract products** — interfaces naming each product type. 2. **Concrete products** — implementations, grouped by variant (a dark button, a dark checkbox; a light button, a light checkbox). 3. **Abstract factory** — an interface/abstract class with one creation method per product type. 4. **Concrete factories** — one per variant, returning a *consistent* set. ```java // 1. Abstract products interface Button { void render(); } interface Checkbox { void render(); } // 2. Concrete products (Dark family) class DarkButton implements Button { public void render() { System.out.println("dark button"); } } class DarkCheckbox implements Checkbox { public void render() { System.out.println("dark checkbox"); } } // (Light family analogous) class LightButton implements Button { public void render() { System.out.println("light button"); } } class LightCheckbox implements Checkbox { public void render() { System.out.println("light checkbox"); } } // 3. Abstract factory: one creator per product type interface ThemeFactory { Button createButton(); Checkbox createCheckbox(); } // 4. Concrete factories: each returns ONE consistent family class DarkThemeFactory implements ThemeFactory { public Button createButton() { return new DarkButton(); } public Checkbox createCheckbox() { return new DarkCheckbox(); } } class LightThemeFactory implements ThemeFactory { public Button createButton() { return new LightButton(); } public Checkbox createCheckbox() { return new LightCheckbox(); } } // Client: depends only on the abstractions class Toolbar { private final Button button; private final Checkbox checkbox; Toolbar(ThemeFactory factory) { // factory injected from outside this.button = factory.createButton(); this.checkbox = factory.createCheckbox(); } void render() { button.render(); checkbox.render(); } } ``` ### Why the client is decoupled The `Toolbar` references only `ThemeFactory`, `Button`, and `Checkbox` — all **abstract**. It contains **no `new ConcreteProduct()`** call. The decision of which family to use is made *outside* the client (whoever constructs the `Toolbar` passes a `DarkThemeFactory` or `LightThemeFactory`). Consequences: - **Swapability:** switching the whole UI to light theme is changing one line where the factory is chosen. - **Consistency:** you cannot accidentally combine a `DarkButton` with a `LightCheckbox`, because one factory only ever produces its own family. This invariant is the pattern's signature value. - **Testability:** in a test you can inject a `FakeThemeFactory` producing stub products. ### Mapping to the JDK - `ThemeFactory` ↔ `DocumentBuilderFactory` (abstract factory). - `createButton()` ↔ `newDocumentBuilder()` (a creation method). - `Button` ↔ `DocumentBuilder` (an abstract product). - Choosing `DarkThemeFactory` ↔ `DocumentBuilderFactory.newInstance()` resolving a provider. ### The cost to keep in mind If the family later needs a **new product type** — say `createMenu()` — you must add that method to `ThemeFactory` *and* implement it in **every** concrete factory. Adding a new *variant* (a `HighContrastThemeFactory`) is cheap; adding a new *product type* ripples across all factories. This asymmetry is the classic Abstract Factory trade-off and the thing to mention to show depth. ### When not to bother If you only have one product type, use a Factory Method or a static factory. If the family never varies, a plain constructor is fine. Reach for Abstract Factory when you have *multiple* related products *and* multiple variants that must stay consistent.
- How does the client receive the right concrete factory?From outside the client: by configuration, a runtime lookup (like JAXP's newInstance()), or dependency injection. The client just declares it needs the abstract factory type; selection of the variant is someone else's responsibility, which is exactly what keeps the client decoupled.
- What breaks if you add a createMenu() product type later?You must add createMenu() to the abstract factory interface and implement it in every existing concrete factory. Adding product types is the expensive direction of change; adding new factory variants is cheap. This asymmetry is the core Abstract Factory trade-off.
saying these in an interview costs you the question
- Putting `new ConcreteProduct()` inside the client — that defeats the decoupling.
- Having one concrete factory return products from different families (mixing dark button + light checkbox) — violates the consistency invariant.
- Collapsing all products into one method — that's Factory Method, not Abstract Factory.
- Claiming adding a new product type is cheap — it forces changes to every concrete factory.