The idea: pick an algorithm by name, call it through an interface
An uploader must compress files before sending them. Which compressor to use is a setting:
compression: gzip in app.yaml. Two classic patterns split this job.
The Strategy pattern puts each algorithm in its own class behind one interface.
The Factory Method turns the config string into the right object, in one place.
Strategy: one class per algorithm
A strategy is an object that does one job in a replaceable way. Here the job is
compress(data), declared by the Compressor interface.
GzipCompressor, ZstdCompressor and NoCompression each implement it.
The client, Uploader, has a field of type Compressor. When it uploads, it calls
compressor.compress(data). The language's dynamic dispatch sends the call to
the class of the object that is actually in the field. The Uploader never asks which one it is.
(Demo: create via the factory and upload)
Doing nothing is a strategy too. NoCompression returns the data as is. It is a
null object: the client needs no if (compressor != null) check.
(Demo: no compression is a strategy too)
Factory Method: one place that says new
Something must still pick the class. CompressorFactory.create(type) reads the type and looks
it up in a small registry: "gzip" → GzipCompressor, and so on. It is the only code that writes
new GzipCompressor(). It returns the object typed as Compressor, so the caller
does not learn the concrete class either.
Strictly, the Gang of Four Factory Method is a method that subclasses override to decide what to create. The version here, a static method with a lookup table, is often called a simple factory. The point is the same: creation is in one place, and everything else depends on the interface.
Swapping at runtime
Because the Uploader holds an interface, the object in the field can change while the program runs:
uploader.setCompressor(factory.create("zstd")). The old object is dropped. The next upload goes
to ZstdCompressor. No Uploader code changed, and nothing was rebuilt.
(Demo: swap the strategy at runtime)
| Config | Class created | photo.png, 1000 KB, becomes |
|---|---|---|
gzip | GzipCompressor | 320 KB |
zstd | ZstdCompressor | 280 KB |
none | NoCompression | 1000 KB |
br (after Add Brotli) | BrotliCompressor | 260 KB |
The sizes are illustrative. Real ratios depend on the data; an already compressed PNG barely shrinks.
If/else vs strategy: the open/closed principle
The right panel writes the same Uploader without the patterns: a type string and one
if … else if … else throw chain inside upload(). It works. But every upload tests
the conditions again, and the client knows every algorithm by name.
The difference shows when a new algorithm arrives. With the patterns, Brotli is a new class plus one
registry line. Uploader is not opened. With the chain, the new branch goes inside
Uploader.upload(), so working, tested code is edited and must be tested again. That is the
open/closed principle: open for extension, closed for modification.
(Demo: add Brotli without touching the client)
Unknown types fail fast
Set compression: lz4. The factory finds no entry and throws
IllegalArgumentException when the object is created, usually at startup. The Uploader keeps
the strategy it had. The if/else version stores "lz4" without complaint and only throws when
an upload reaches the final else, maybe hours later.
(Demo: unknown type, the factory fails fast)
When a switch is fine
A pattern adds classes and an indirection to read through. If there are two cases that will never grow,
a plain if is clearer. The strategy pays off when cases are added over time, when they are
chosen by config or by the user, or when each one needs its own tests. Many languages also let you pass a
function instead of an object, which is the same idea with less code.
See also SOLID Principles for open/closed and dependency inversion, and Hexagonal Architecture, where adapters are chosen the same way at startup.
See also Inheritance vs Composition: behaviour objects instead of subclasses, swapped at runtime.
What the page leaves out
Real code often registers strategies without editing the factory at all: a plugin list, Java's
ServiceLoader, or a dependency injection container that picks the class by name. Strategies
with settings (gzip level, zstd window size), thread safety when the field is swapped while uploads run,
and the Abstract Factory pattern, which creates whole families of related objects, are also left out.