Four Files to Reach a Constructor

orderFactory.create(request) goes through an interface with exactly one implementation and ends at a plain new. This is about when a design pattern stops being a workaround for the language and turns into ceremony, with the factory as the worked case. Java 8 and C# 9 took much of the catalogue back into the language, and the classes those patterns produced are still being written, because a factory reads as foresight and a constructor does not.

Patterns Are Language Relative

In most codebases I have worked in there is a call that cannot be followed. Something like orderFactory.create(request), where the factory is an interface, the interface has exactly one implementation, and that implementation calls a constructor with the arguments it was handed and returns the result. Four files and two indirections to reach a new. Jump-to-definition lands on the interface, which knows nothing. To find out what actually runs, you set a breakpoint.

The person who wrote that was not showing off. They were doing what they had been taught, by a book that deserves its reputation, by a decade of style guides that summarized the book badly, and by interviews where "tell me about a design pattern you have used" is a question with a right answer. Nobody ever got a comment in review for adding an interface. That asymmetry did most of the work here.

Here is what I think a design pattern actually is. It is a workaround for something the language cannot express directly, written down and given a name so that people can recognize each other's workarounds without reading the whole file. Both halves matter. The workaround part is why it exists, and the name part is why it spreads, and the name outlives the reason by a long way.

That is not a hostile reading, and it is not mine. The authors of Design Patterns put it in their own introduction in 1994: the choice of language shapes what is easy and therefore which patterns you need, their examples are in C++ and Smalltalk because those were the languages they had, and a version of the book written for procedural languages might well have contained patterns named Inheritance, Encapsulation and Polymorphism. Twenty-three patterns, and a note at the front saying the list is relative to the tools.

Peter Norvig took that further in a talk he first gave in May 1996 and put online two years later, arguing that sixteen of the twenty-three are invisible or substantially simpler in a language like Lisp or Dylan. You can argue with the count. The direction is the whole subject: a pattern is a shape the language forced you into, so a pattern has a life. It is discovered, it becomes vocabulary, the language grows the feature, and then it is noise that survives because it is teachable and easy to praise.

The Factory, Taken Apart

The factory is the clearest case in the catalogue, because the real one and the ceremonial one look identical in review.

Start with what a factory is actually for. It defers a construction decision: which concrete type, built which way, chosen at which moment. When a message arrives with a discriminator field and one of six handlers has to be built from it, the caller genuinely must not know the set, and the decision genuinely cannot be made where the object is used. That is a real need, met by a real pattern, and I would write it without a second thought.

Then there is the factory that defers nothing. One implementation, one call site, one constructor, wrapped in a method wrapped in a class wrapped in a file. Nothing varies. No decision is being postponed, because there is no decision. What is left is the indirection of the pattern with none of the choice the indirection was buying, kept because construction felt like it deserved a ceremony and a plain new felt too blunt to be professional.

Then comes the second-order version. A factory that produces factories, AcmeConnectionFactoryProvider and its relatives, is almost always a configuration problem that somebody solved with types. What actually varies is a string in a config file and a couple of numbers. Modeled in the type system, every new case becomes a class, a registration, and a build, when it could have been a line of configuration and a restart.

The container already did this. By 2021 most large Java and C# codebases run a dependency injection container, which is a factory somebody else wrote, tested, and documented. A hand-rolled factory whose whole job is to pass three constructor arguments through is not an abstraction over construction. It is a second, worse container with one entry.

The tell is in the name. A useful factory can be described in one sentence that names what varies: it picks the handler for the message type. A ceremonial one can only be described by its mechanism, because there is nothing else to say about it. If the honest sentence is "it makes the thing", the pattern is doing no work, and the name is the only part of it that survived.

Why It Reads as Competence

A pattern is legible. A reviewer opening a file can see that you used one; the shape is distinctive and it has a name they know. What the same reviewer cannot see, in the same two minutes, is that you did not need it. Absence of structure looks like absence of thought, and structure that is not required looks like foresight. Those two mistakes are not priced the same, and everybody knows it.

So the incentive runs one way. "Uses a factory" reads as competence and "calls a constructor" reads as someone who has not thought about extension yet, and the person writing the code has ten minutes and a queue of tickets behind this one. They are responding accurately to how the work is judged, which is what people do.

The cost lands in a different place and much later. It is paid in small amounts by everyone who reads the file afterward, none of whom is in the review, most of whom have not been hired yet. That is the structure of every decision that goes wrong quietly: the benefit is visible now, to the person deciding, and the bill is spread across strangers over years.

Which is why telling people to stop writing factories does not work, and why I have watched it not work. The lever is the review, and it is one question rather than a rule: what varies here, and can you name it. Asked kindly and asked every time, it removes more ceremony than any style guide ever has, because it changes what the code must justify.

The Seam You Did Not Need

If you cannot name the second implementation, you do not have one. The seam exists to let a second thing slot in. So name it. Not a category, an actual thing: the other payment provider, the on-disk store used in tests, the format the partner insists on. If the answer is "we might need to swap it out one day", you are not building an abstraction, you are building a hope, and the hope charges rent from the day it is merged.

The guessed seam is usually in the wrong place. Variation, when it finally arrives, rarely appears where anybody predicted. What you get is two abstractions instead of one: the interface you guessed, and the real one that had to be added beside it because reworking the first would touch everything. The unused seam does not quietly retire. It stays, because things now depend on it, and it teaches the next person a boundary that was never true.

The seam is cheap to add later. This is the part that makes speculation a bad trade rather than merely an unnecessary one. In a codebase with tests, extracting an interface and introducing a factory is a mechanical refactor that the IDE largely performs for you. Paying today for something whose later price is small, in exchange for a benefit that may never arrive, is a bet with the odds visible on the ticket.

The tax is on reading, and it never stops. Indirection is written once and read for years. Every layer between the call and the code defeats the reader's ability to follow a program by following the calls, and in a deeply factoried codebase the question "what actually runs here" stops being answerable by jumping to the definition and starts requiring a debugger. That is not an aesthetic complaint. It is an hour, repeatedly, for everyone who arrives afterward.

The Language Grew the Feature

Java 8 in 2014 is the clean example, because nobody framed it as a change to patterns at all. Once a function is a value you can pass, Strategy is a parameter, Command is a closure, and a large part of the behavioral catalogue turns into a method reference on the line where it is used. No announcement was made. Those classes simply stopped being written, and nobody misses the folder they lived in.

C# 9 arrived with .NET 5 on 10 November 2020 carrying records, init-only setters, relational and logical patterns, and target-typed new. Consider what a record replaces: the fields, the constructor, the equality, the hash code, the printable form, and the builder somebody wrote because the constructor had nine parameters. All of that was boilerplate the language demanded, and much of the ceremony around it existed to manage the boilerplate rather than the problem.

Java 16, in March 2021, made records final under JEP 395 after two releases in preview, and made pattern matching for instanceof final under JEP 394. That second one is quieter and more interesting. Test, cast, assign to a variable, use it: three lines that existed only because the language could not do the obvious thing, and around which a certain amount of visitor-flavored dispatch was justified for years. It is now one line and the cast is gone.

Sealed classes are in their second preview in the same release, which I would not build anything on yet, but the shape being assembled is visible from here: a closed set of types, each a record, matched by the compiler. That is a data declaration doing what a small hierarchy of classes and an accept method used to do. The patterns did not turn out to be wrong. The language moved underneath them, exactly as the book's own introduction warned it could.

When It Is Still Right

A second implementation exists today. Two payment providers, two storage backends, the queue in production and the in-memory one in tests. The seam is not speculative when both sides of it are running. This is the ordinary case and it is most of the honest factories I have seen, which is worth saying, because the argument against ceremony gets quoted as an argument against abstraction and they are not the same argument.

Somebody else supplies the boundary. A plugin interface, an extension point, anything where the set of implementations is not yours to enumerate because a customer or another team fills it in. Deferring the decision is not a design preference here, it is the product. The factory and its registry are the mechanism by which somebody who cannot modify your code gets to change what it does.

A decision genuinely happens late. The concrete type is chosen by data: a field on the message, the tenant's configuration, which region the request landed in. No amount of language improvement collapses that, because the variation is in the input rather than in the source. When the decision truly cannot be made at the call site, deferring it is the only correct answer and the pattern is doing exactly its job.

The tests actually require a seam. Construction that reaches for the clock, the network, or the filesystem has to be substitutable or the test suite is slow and flaky. Worth noting that a parameter often does this better than a factory, since it takes the dependency out of construction entirely rather than moving it one file away.

The same class gets two verdicts. The uncomfortable part is that identical code is correct in one codebase and ceremonial in another, and nothing inside the file tells you which. That is why blanket rules fail in both directions. "Always use a factory" and "never use a factory" are the same mistake, which is answering a question about a specific codebase with a fact about neither.

The Overcorrection

The backlash produced a dogma of its own, and it is not obviously the better one. "Just call the constructor" is excellent advice right up to the moment you need the seam, and what happens then is not that somebody adds an interface. What happens is a boolean parameter, then a second boolean, then a switch that grows for three years and cannot be extended without editing the same file every time, which is precisely the situation the catalogue was written about.

Teams that mock patterns reinvent them constantly, in worse form, under names that hide what they are. A map of lambdas keyed by string is a Strategy with no type checking and nowhere to hang documentation. An object with a mode flag and four branches is an Abstract Factory that cannot be tested a piece at a time. The problems the book describes recur because they are real, and refusing the vocabulary does not stop you solving them, it only stops you naming what you did.

And the vocabulary is the durable half. Saying "this is an Adapter" ends a design discussion in one sentence between two people who both know the word, and knowing the word costs nothing at runtime. I would rather work with somebody who has read the catalogue and applies almost none of it, because that person can tell me what they chose not to do.

So the claim I opened with is too strong and I want to shrink it before the end. The language absorbing a pattern does not retire the idea, it retires the implementation. Records do not decide for you whether a thing has identity or is just a value, and lambdas do not tell you where behavior belongs. What died is the class, not the judgment, and my complaint is narrower than it sounded: not against knowing patterns, only against applying them by default.

I cannot give you a rule out of this, and I distrust the version of me that tries. "One implementation means delete the factory" is a pattern applied by default, which is the thing I have spent the whole piece objecting to, and it will be wrong in the codebase where the second implementation lands next month. The only question that travels is what varies here, and the only place it can be answered is in front of the code with somebody who knows the system.

What stays with me is the breakpoint. Somebody three years from now, on a Tuesday, stopping the program to learn which constructor runs, because the answer was moved out of the file where the question is asked and put behind a name that no longer defers anything. The pattern was good advice when the language could not say what it meant. The language can say it now, and the ceremony is still there, being read.