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.

Class diagram: app.yaml says gzip; CompressorFactory reads it, looks it up in its registry and creates GzipCompressor; Uploader holds a field of type Compressor; GzipCompressor, ZstdCompressor and NoCompression implement the Compressor interface; a call goes from Uploader through the interface to GzipCompressor
The factory chooses the class once; the client only ever talks to the interface.

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)

ConfigClass createdphoto.png, 1000 KB, becomes
gzipGzipCompressor320 KB
zstdZstdCompressor280 KB
noneNoCompression1000 KB
br (after Add Brotli)BrotliCompressor260 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)

Adding Brotli two ways. Left, if/else: a new else-if branch is inserted inside Uploader.upload, marked edited, and the whole Uploader is retested. Right, strategy: a new BrotliCompressor class and one new registry line in CompressorFactory; Uploader is unchanged
Adding Brotli: the if/else edits the client, the strategy adds a class beside it.

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.