Abstract Factory — Find the Bug¶
12 buggy snippets distributed across Go, Java, Python.
Bug 1: Mixed Variants Within One Factory (Java)¶
class WindowsGuiFactory implements GuiFactory {
public Button createButton() { return new WindowsButton(); }
public Checkbox createCheckbox() { return new MacCheckbox(); } // BUG
}
Symptoms: UI looks half Windows, half Mac. No compiler error — both classes implement the right interface.
Find the bug
`createCheckbox` returns a `MacCheckbox` instead of a `WindowsCheckbox`. Type system doesn't catch family mismatches.Fix¶
Lesson¶
Test every Concrete Factory: assertThat(f.createCheckbox()).isInstanceOf(WindowsCheckbox.class);. Family consistency is structural, not enforced.
Bug 2: Forgot to Update One Factory (Java)¶
interface GuiFactory {
Button createButton();
Checkbox createCheckbox();
Slider createSlider(); // newly added
}
class WindowsGuiFactory implements GuiFactory {
public Button createButton() { return new WindowsButton(); }
public Checkbox createCheckbox() { return new WindowsCheckbox(); }
public Slider createSlider() { return new WindowsSlider(); }
}
class MacGuiFactory implements GuiFactory {
public Button createButton() { return new MacButton(); }
public Checkbox createCheckbox() { return new MacCheckbox(); }
// BUG: missing createSlider — won't compile
}
Symptoms: Compile error: MacGuiFactory must implement createSlider.
Find the bug
This is the *abstract factory dilemma*. Adding a new product type forces updates to *every* Concrete Factory.Fix¶
Add createSlider to all Concrete Factories. Or, for backward compat, use a default method:
interface GuiFactory {
Button createButton();
Checkbox createCheckbox();
default Slider createSlider() {
throw new UnsupportedOperationException("This factory doesn't support sliders");
}
}
Lesson¶
Plan family axes carefully. New product types are expensive.
Bug 3: Direct Constructor in Client Code (Java)¶
public void renderToolbar(GuiFactory factory) {
Button save = factory.createButton();
Button cancel = new HtmlButton(); // BUG
save.render();
cancel.render();
}
Symptoms: cancel is always HTML regardless of the factory.
Find the bug
Direct `new HtmlButton()` bypasses the factory. Family consistency violated.Fix¶
Lesson¶
Search for new ConcreteX( in client code. None should remain after Abstract Factory adoption.
Bug 4: Race in Singleton Concrete Factory (Java)¶
public class WindowsGuiFactory implements GuiFactory {
private static WindowsGuiFactory INSTANCE;
private WindowsGuiFactory() {}
public static WindowsGuiFactory getInstance() {
if (INSTANCE == null) {
INSTANCE = new WindowsGuiFactory(); // BUG: race
}
return INSTANCE;
}
}
Symptoms: Under concurrent first-access, two factories might be created.
Find the bug
Lazy singleton without synchronization. Both threads see null, both construct.Fix¶
private static class Holder { static final WindowsGuiFactory INSTANCE = new WindowsGuiFactory(); }
public static WindowsGuiFactory getInstance() { return Holder.INSTANCE; }
Or use enum singleton. See Singleton.
Lesson¶
If your factory is a singleton, use a thread-safe singleton implementation.
Bug 5: Cross-Product Reference Breaks Type Safety (Java)¶
class WindowsCheckbox implements Checkbox {
private final Button parent;
public WindowsCheckbox(Button parent) { this.parent = parent; }
}
class WindowsGuiFactory implements GuiFactory {
public Button createButton() { return new WindowsButton(); }
public Checkbox createCheckbox() {
return new WindowsCheckbox(new MacButton()); // BUG
}
}
Symptoms: WindowsCheckbox accepts a parent Button, but it's a MacButton — visual confusion.
Find the bug
The factory creates a wrong-variant cross-reference. Type system permits because both implement `Button`.Fix¶
Or pass a typed reference (with a generic-typed factory).
Lesson¶
Cross-product references are a code smell. If unavoidable, the factory must coordinate them within the same family.
Bug 6: Product Stored in Wrong Variant Field (Python)¶
class App:
def __init__(self, factory: ThemeFactory):
self.button: WinButton = factory.make_button() # BUG: type hint lies
self.checkbox = factory.make_checkbox()
Symptoms: mypy warnings ignored; runtime AttributeError when WinButton-specific method is called on a MacButton.
Find the bug
The type annotation `WinButton` is too narrow. `factory.make_button()` returns `Button`, not specifically `WinButton`.Fix¶
Lesson¶
Type hints must match the abstract factory's return type, not assume a specific variant.
Bug 7: Factory Caches Mutable Products (Go)¶
type GUIFactory interface{ CreateButton() Button }
type winFactory struct{}
var sharedButton = &winButton{label: ""}
func (winFactory) CreateButton() Button {
return sharedButton // BUG: shared mutable
}
Symptoms: Setting b1.label = "Save" also changes b2.label because they're the same object.
Find the bug
The factory returns the same instance every call. Callers expect fresh objects (or at least immutable shared ones).Fix¶
Or document that the product is immutable and never mutated.
Lesson¶
Document factory contract: fresh per call, or shared. Cached mutable singletons are a bug factory.
Bug 8: ServiceLoader Returns Wrong Concrete (Java)¶
ServiceLoader<GuiFactory> loader = ServiceLoader.load(GuiFactory.class);
GuiFactory chosen = loader.iterator().next(); // BUG: takes the first one
Symptoms: On Mac, the app uses Windows widgets because the Windows JAR was loaded first.
Find the bug
`ServiceLoader` doesn't filter — it returns whatever providers register. The first one isn't necessarily the right one.Fix¶
GuiFactory chosen = loader.stream()
.map(ServiceLoader.Provider::get)
.filter(f -> f.platform().equals(detectOs()))
.findFirst()
.orElseThrow();
Lesson¶
ServiceLoader discovers; filtering is your job. Add a platform() or name() method to the factory interface to enable selection.
Bug 9: Plugin Classloader Leak (Java)¶
public class PluginManager {
private static final List<GuiFactory> registered = new ArrayList<>();
public void load(URL jar) throws Exception {
URLClassLoader cl = new URLClassLoader(new URL[]{jar});
Class<?> c = cl.loadClass("com.x.MyFactory");
registered.add((GuiFactory) c.getDeclaredConstructor().newInstance());
}
public void unload(int idx) {
registered.remove(idx); // BUG: doesn't release classloader
}
}
Symptoms: Memory grows after each plugin load/unload cycle. Eventually OutOfMemoryError: Metaspace.
Find the bug
Removing the factory from the list isn't enough — internal references (caches, classloaders, registered services) keep the plugin classloader alive.Fix¶
private static class PluginEntry {
final URLClassLoader cl;
GuiFactory factory;
PluginEntry(URLClassLoader cl, GuiFactory f) { this.cl = cl; this.factory = f; }
}
public void unload(int idx) throws IOException {
PluginEntry e = registered.remove(idx);
e.factory = null;
e.cl.close();
}
Plus: ensure all factory products are released before unloading.
Lesson¶
Plugin systems require explicit lifecycle. Removing a reference doesn't free the classloader — close the loader and verify GC reclaims it.
Bug 10: Abstract Factory Mistake — Returns Concrete Type (Java)¶
interface GuiFactory {
WindowsButton createButton(); // BUG: concrete return type
Checkbox createCheckbox();
}
class MacGuiFactory implements GuiFactory {
public WindowsButton createButton() { return new WindowsButton(); } // forced!
public Checkbox createCheckbox() { return new MacCheckbox(); }
}
Symptoms: Mac factory must return WindowsButton. Defeats the entire pattern.
Find the bug
The interface declares a concrete return type. Concrete Factories are bound to that concrete type.Fix¶
Lesson¶
Abstract Factory's return types must always be abstract.
Bug 11: Python Factory Returns From Wrong Family (Python)¶
@theme("dark")
class DarkTheme(ThemeFactory):
def make_button(self): return DarkButton()
def make_checkbox(self): return LightCheckbox() # BUG
Symptoms: A "dark" theme has a light checkbox. UI inconsistency.
Find the bug
Same as Bug 1 — wrong variant. Python's dynamic typing makes this even harder to catch.Fix¶
Lesson¶
Per-Concrete-Factory unit test. Python doesn't even give compile-time hints.
Bug 12: Stale Product After Hot-Swap (Java)¶
public class App {
private final GuiFactory factory;
private final Button persistentButton; // stored at construction
public App(GuiFactory factory) {
this.factory = factory;
this.persistentButton = factory.createButton();
}
public void switchTheme(GuiFactory next) {
// BUG: doesn't refresh persistentButton
// (assume reflection sets factory)
}
}
Symptoms: After theme switch, persistentButton is still the old style.
Find the bug
Products created at one time can't reflect later factory swaps. The button is a snapshot.Fix — Don't store products¶
Fix — Notify and recreate¶
public void switchTheme(GuiFactory next) {
factory = next;
persistentButton = factory.createButton(); // refresh
notifyObservers();
}
Lesson¶
Hot-swap is a contract: existing products stay; future ones are new variants. Or, maintain a registry of products to refresh.
Practice Tips¶
- Test family consistency for every Concrete Factory.
- Hunt for
new ConcreteX(in client code. - Run
go test -race,mypy, JMH to catch language-specific bugs. - Log factory selection at startup — "Using AwsFactory" — so deployment errors are obvious.
← Tasks · Creational · Roadmap · Next: Optimize