- Low Level Design
- /
- Factory Method Pattern
Course content
The Factory pattern centralises object construction so that callers do not have to know which concrete class they are receiving. Instead of every part of the codebase saying new Circle(...) or new Square(...) directly, callers ask a factory for the shape they want by name (or by some other discriminator) and the factory returns an instance of the right subclass through the abstract supertype.
The value is not novelty in the construction call itself. It is that the dispatch from "what kind of thing" to "which class" lives in exactly one place. When a new kind of thing is added, the factory changes. Every caller continues to ask the factory in the same way and continues to receive the abstraction. The page starts from the mess the pattern exists to collapse: the same construction dispatch pasted into two files.
Two different files contain almost the same dispatch logic with slightly different naming conventions. When the design adds an Ellipse shape, both files have to be edited. Reviewing either change requires understanding both, because the dispatch must stay consistent between them.
The broader issue is that every caller now imports every concrete shape class. The abstraction (Shape) is not really doing its job: callers are still coupled to the concrete subclasses, just temporarily long enough to construct them. Centralising the construction is what lets the rest of the program forget the concrete classes exist.
What this costs in production
An Ellipse shape was added and the dispatch if/else in ShapeRenderer was updated, but the near-identical ladder in ShapeImporter was missed because the two used different tokens (circle versus CIRCLE). Imported files with ellipses threw Unknown shape in production while the same shapes rendered fine in the editor. The duplicated construction logic had drifted, and the gap only surfaced on the import path.
The renderer and the importer are one instance of a shape that repeats wherever objects are constructed from names, tokens, or configuration values. You spot the factory opportunity from symptoms like these.
| Smell | What it looks like | Why a factory is the answer |
|---|---|---|
| Type-based new-statement chains in callers | if (type.equals("circle")) new Circle(...); else if ... | The dispatch is duplicated wherever the chain appears |
| Many call sites need to know every concrete subclass | Each module imports Circle, Square, Rectangle directly | A new subclass means edits to every caller |
| Construction requires a non-trivial sequence of steps | new Circle(parseRadius(input), validate(input), normalise(input)) | Centralising the recipe avoids errors when callers skip a step |
| Callers depend on a concrete class because they need it for construction, but only use it through the abstraction afterwards | Circle c = new Circle(...); Shape s = c; doSomething(s); | The dependency on the concrete is purely an artifact of construction |
| An external configuration string maps to a class | JSON or YAML names a strategy, the code has to find the right class | A factory with a switch (or registry) is the natural place to hold that mapping |
There is now exactly one file in the codebase that knows about every concrete Shape subclass: ShapeFactory. Every other file works with the Shape abstraction. When a new shape is added, the factory grows by one case and the rest of the system is untouched.
Note what the refactor does and does not buy. It buys centralisation: one place to look, one place to change, one place to review. It does not buy extensibility in the OCP sense. The factory still has a switch; adding a new shape still requires editing the factory. The Factory pattern complements OCP rather than implementing it. If you also want truly open-for-extension dispatch, you can pair the factory with a registry: callers register their constructors with the factory at startup, and the factory itself never has to be edited again. That is a step beyond what most exercises ask for, but it is the natural next move once you have the basic shape in place.
With the refactor in hand, it is worth sorting out the names, because the word "factory" is overloaded in design-pattern literature. What this page built is sometimes called Simple Factory: a static method on a dedicated class that takes a discriminator and returns the abstraction (ShapeFactory.create("circle", 5)). It is not in the GoF book by that name but is by far the most common form in real code, including framework entry points like Calendar.getInstance() and DocumentBuilderFactory.newInstance().
The GoF book reserves the name Factory Method for a slightly different shape: an instance method on a class that subclasses override to produce different products. Connection.createStatement() returning different Statement subtypes for different databases is a classic example. The Abstract Factory pattern goes one step further and creates families of related products together (UiToolkit.createButton() and UiToolkit.createMenu() from the same factory instance).
For most everyday needs, Simple Factory is the right tool. Reach for the GoF Factory Method when you have a hierarchy of factories that mirror a hierarchy of products; reach for Abstract Factory when products come in matched sets. The exercise on this page uses the Simple Factory form because that is the one that solves 90% of real cases.
Factories are one of the most common patterns in every standard library, and the same shapes recur across languages.
In Java, Calendar.getInstance(), NumberFormat.getInstance(), and Currency.getInstance("USD") are Simple Factory: a static method that returns the right subtype from a private dispatch. The caller does not know whether it gets a GregorianCalendar or a BuddhistCalendar; the factory picks based on locale. DocumentBuilderFactory.newInstance() goes further and inspects system properties and the classpath to decide which XML parser implementation to hand back. Spring's BeanFactory is the same idea taken to its conclusion: an entire configuration-driven factory, which is why most Spring code never types new for application-layer objects.
Python has the same shapes with less ceremony. logging.getLogger("app") is a factory with a registry layered on top: ask twice for the same name and you get the same logger object, exactly like SLF4J's LoggerFactory.getLogger in Java. pathlib.Path("/tmp/x") is a textbook simple factory: the class you call is Path, but the instance you receive is a PosixPath or a WindowsPath depending on the operating system, and your code never needs to know which. Classmethod constructors like datetime.fromtimestamp(...) and Decimal.from_float(...) are the static-factory-instead-of-constructor idea: a named entry point that carries intent a bare constructor cannot.
In C++, the standard shape is a free factory function returning std::unique_ptr<Base>, which is exactly what our ShapeFactory::create does; returning an owning smart pointer through the abstract type is the core-guidelines way to hand a caller a subclass without exposing it. When teams need the open-ended version, they keep a registry, a std::map<std::string, std::function<std::unique_ptr<Shape>()>>, that plugins populate at startup.
The GoF Factory Method form, an overridable instance method that produces a product, is quietly everywhere: JDBC's Connection.createStatement() returns a driver-specific Statement; every Java collection's iterator() returns its own iterator class; Python's iter(obj) calls __iter__, which each container implements to return its own iterator type; and a C++ container's begin() does the same with its own iterator. Every time a container hands you "its" iterator through a uniform call, you are using a factory method.
| Pros | Cons |
|---|---|
| One file owns type-name-to-class dispatch; new types are a single-file change | The factory itself still has a switch and is still edited per new type (unless paired with a registry) |
| Callers depend only on the abstraction, not on every concrete subtype | Adds an indirection point and a vocabulary (`createX`, `getInstance`, `newInstance`) that needs to be learned |
| Construction recipe (parsing, validation, normalisation) lives in one place | For one-class factories, the indirection is pure ceremony with no payoff |
| Plays well with config-driven object selection: a YAML name maps to a class via the factory | Reflection-based variants (`Class.forName(type).newInstance()`) trade compile-time safety for runtime crashes on typos |
| Static-factory naming options (`of`, `from`, `valueOf`, `getInstance`) carry intent that constructors cannot | Static factories cannot be subclassed; if you need polymorphism over the factory itself, use Factory Method or Abstract Factory |
Factory pays off when there is genuine type-based dispatch: callers receive a string or a config value and need a corresponding object. If construction is straightforward and there is only ever one concrete class, a constructor call is fine. Wrapping every new in a factory just to feel patternful adds indirection with no payoff and obscures the actual creation site.
A related trap is reaching for reflection (Class.forName(type).newInstance() in Java, globals()[name]() in Python) to make the factory "open-ended". This trades a small amount of explicit code for runtime errors that surface deep in production. A switch with a clear default branch is better than a reflective lookup that fails on a typo. If you really need open-ended registration, use a registry of constructors keyed by string, not raw reflection on user-supplied class names.
Three flavours of question recur. The first is design: "design a logging library", "design a notification system", "design a database connection pool". The expected first move is to identify the abstraction (Logger, Notifier, Connection) and propose a factory entry point that hides the choice of concrete implementation. The follow-up tends to be about adding a new backend (a new logger sink, a new notification channel); that is your cue to discuss whether the factory grows a switch or whether you would pair it with a registry for open-ended extension.
The second is comparison: "static factory method vs. constructor" (Effective Java item 1), "factory vs. builder", "Simple Factory vs. Factory Method vs. Abstract Factory". For static-factory-vs-constructor the talking points are: factories have meaningful names (Boolean.valueOf vs. new Boolean(...)), factories can return cached or subtype instances, factories can be implemented behind an interface. For factory-vs-builder: Builder is for objects with many optional parameters; Factory is for choosing between concrete types. For the three factory flavours: Simple Factory is a static method; GoF Factory Method is an instance method overridden by subclasses; Abstract Factory creates families of related objects from one factory instance.
The third is about extensibility: "how would you let plugins register their own implementation?". The expected move is to evolve the factory from a hardcoded switch to a registry: a Map<String, Supplier<Foo>> in Java, a dict of callables in Python, a map of factory lambdas in C++, populated by callers at startup. This pairs with the Open/Closed Principle and turns the factory into a true extension point. The trap is to reach for raw reflection, which surfaces typos as runtime exceptions and opens a security hole if the class names come from user input.
The companion exercise gives you a Shape hierarchy and an empty ShapeFactory.create method. Implement the factory so that callers can ask for shapes by name and receive Shape references. The validator confirms that each known type produces the right subclass with the right area, that unknown types throw IllegalArgumentException, and that the declared return type stays the abstraction.
Open the exercise: Factory Method: Centralise Shape Creation.