skip to content

You have an established Abstract Factory interface with several concrete factories in production. What happens when you need to add a brand-new product kind to the family, and how would you mitigate the cost?

level: seniorimportance: should knowfreq 52%

answer

  1. rows cheap, columns expensive
  2. new variant = OCP win; new kind = breaking change
  3. default method ⇒ compile-time → runtime failure
  4. split by cohesion (ISP) loses one-factory consistency
  5. registry: open kinds, lost static completeness

basics

~20 s

Adding a new product kind means adding a method to the factory interface, so every existing concrete factory must implement it. Adding a new variant is cheap; adding a new kind is expensive. Mitigations include default implementations, splitting the interface, or a registry keyed by product type.

solid answer

~60 s

Abstract Factory is extensible along one axis only. Adding a **variant** means writing one new class — no existing code changes (Open/Closed satisfied). Adding a **product kind** means editing the abstract factory interface, which breaks every implementation, including any written by downstream consumers of a published library. Mitigations, roughly in order of preference: (1) question whether the new kind really belongs to the family — if only some variants support it, it does not; (2) give the interface a default implementation that throws `UnsupportedOperation` or returns a null-object/absent value, trading compile-time safety for source compatibility; (3) split the fat factory into several smaller family interfaces along cohesion lines (Interface Segregation), letting a variant implement only what it supports; (4) replace the method-per-kind shape with a generic lookup — `<T> T create(Class<T> kind)` or a registry of creators — which makes adding kinds cheap but loses static typing and the compile-time guarantee that a variant is complete. Choose based on whether the interface is internal (just refactor it) or a public extension point (compatibility rules).

go deeper

for a junior

Say that the new method must be added to the interface and therefore implemented by every existing factory, and that adding a whole new variant is much cheaper by comparison.

for a middle

Name the asymmetry explicitly in Open/Closed terms and offer default methods as the compatibility escape hatch, noting it converts a compile error into a runtime one.

for a senior

Weigh internal vs. published interfaces, offer interface splitting and registry lookup with their concrete costs, and flag that splitting weakens the single-factory consistency guarantee.

for a principal

Frame it as the expression problem (Abstract Factory vs. Visitor as duals), reason about API-evolution policy for extension points (versioned sub-interfaces, abstract base classes, capability negotiation), and decide based on who owns the implementations and what the deprecation window costs.

### The grid, and why extensibility is asymmetric An Abstract Factory design is a two-dimensional grid: ``` createButton createCheckbox createMenu DarkFactory Dark… Dark… Dark… LightFactory Light… Light… Light… HighContrast… HC… HC… HC… ``` - **Adding a row (a new variant)** = one new class implementing an existing interface. Nothing else compiles differently. This is the pattern's selling point and a textbook illustration of the **Open/Closed Principle** (open for extension, closed for modification). - **Adding a column (a new product kind)** = a new method on the interface, so *every row* must gain a cell. If there are 3 variants that is annoying; if the interface is published to plugin authors, it is a **breaking change** — their code no longer compiles against the new version. This asymmetry is intrinsic, not a flaw in a particular implementation. Any design that enumerates operations in an interface is cheap to extend with implementations and expensive to extend with operations — the same trade-off as the Visitor pattern (easy to add operations, hard to add node types; Abstract Factory is the mirror image). ### Mitigation 1 — challenge the family boundary The cheapest fix is often to notice the new kind is not actually part of the family. Signs: only one or two variants can meaningfully produce it; nothing in the client needs it to match the other products' variant. Then it belongs in its own factory or plain construction, and the existing interface stays untouched. ### Mitigation 2 — default methods / optional operations Many languages allow interface methods with bodies (default methods, abstract classes with a base implementation, mixins). Adding `createSlider()` with a default that throws `UnsupportedOperationException` or returns an absent/no-op value keeps every existing implementation compiling. Trade-off: you have moved a compile-time error to runtime. The type system no longer tells you which variants are complete; clients must handle the unsupported case (feature check, capability query, or catching the exception — the first two are far better). This is exactly the tension the **Interface Segregation Principle** warns about: "clients should not be forced to depend on methods they do not use", and its cousin, don't force implementers to stub out operations they cannot honor. In inheritance terms, a factory that throws on half its methods is a **Liskov Substitution Principle** smell: it is not truly substitutable for the interface it claims. ### Mitigation 3 — split the interface If product kinds cluster (`ControlsFactory` for button/checkbox/slider, `ChromeFactory` for menu/toolbar/statusbar), split them. A concrete variant class can implement both, or several classes can be composed. Adding a kind now touches only implementers of the smaller interface. Cost: clients that need both must be given both (or a composite), and the *single* consistency guarantee weakens — two independently injected factories can be from different variants unless you keep a bundling type that hands out both. ### Mitigation 4 — generic / registry-based creation Replace the enumerated methods with a lookup: ``` interface ProductFactory { <T> T create(ProductKind<T> kind) // or: Optional<T> create(Class<T>) } ``` or a registry mapping kind → creator function that variants populate at construction. - Adding a kind: no interface change at all. - Cost: the compiler can no longer verify a variant is complete; return types need generics gymnastics or casts; discoverability drops (you can no longer read the interface to learn what the family contains); refactoring tools lose track of usages. It also invites a service-locator style where dependencies become invisible. Use it when kinds are genuinely open-ended and supplied by plugins; avoid it when the family is a small fixed set. ### Versioning strategies for a published interface If the abstract factory is a public extension point, the usual library tactics apply: introduce `GuiFactoryV2 extends GuiFactory` with the new method and have the framework check `instanceof`/capability at runtime; or ship an abstract base class that implementers are told to extend, so new methods can be added with defaults without breaking anyone. Both trade purity for compatibility. ### The decision, summarized Ask two questions. *Is the interface internal?* If yes, just add the method and fix all implementations in the same commit — the compiler will list them, and this is the safest outcome. *Is the new kind universal across variants?* If no, do not put it in the shared interface at all. Only when the interface is public **and** the kind is universal do you need defaults, versioned sub-interfaces, or a registry.

  • Which SOLID principle does adding a product kind violate, and which one does adding a variant honor?
    Adding a variant honors Open/Closed — extension by new class, no modification. Adding a kind forces modification of the interface and all implementers, and if implementers stub it out with 'unsupported' errors it also strains Interface Segregation and Liskov Substitution.
  • How is this trade-off related to the Visitor pattern?
    They are mirror images of the same expression problem. Abstract Factory makes new variants (implementations) cheap and new operations (product kinds) expensive; Visitor makes new operations cheap and new element types expensive.
  • If the factory interface is internal to one codebase, is any mitigation needed?
    Usually not. Add the method and let the compiler enumerate every implementation to fix in the same change — that is a feature, not a cost, and it preserves the guarantee that every variant is complete.

saying these in an interview costs you the question

  • Claiming Abstract Factory fully satisfies the Open/Closed Principle without qualifying which axis.
  • Adding the method and stubbing it with a thrown exception everywhere without acknowledging the shift to runtime failure.
  • Jumping straight to a reflective `create(Class)` registry for a fixed family of three product kinds.
  • Splitting the interface but forgetting that clients can now be injected two factories from different variants, reintroducing the mixing bug the pattern existed to prevent.
  • Assuming the change is free because 'the IDE will find the compile errors' when the interface is a published plugin API.

context