There's a few common patterns in software development that I consider an antipattern to highly effective code. Mostly it's Factories and Builders.
Guess what this ramble is about?
Builders are an Anti-Pattern
There's more than one reason they are an anti-pattern. My favorite goes back to a phone interview I had. There was a question about design patterns. I don't remember which flavor of question it was. My favorites, what I have used, what could I name... Whatever flavor, one I said was the Builder. I was using it REALLY heavily in a project at the time. I was really familiar with it.
The interviewer followed up with a question I've used in similar contexts, "What the downside to that pattern?"
The biggest thing I remember is testability.
Builders themselves tend to be easy to test. They tend to be a single class. Great. What about the classes that USE the builder?
You start to get coupled to the implementation details. Your tests start to get coupled to what the Builder needs, not just focused on the behavior under test. Builders make it harder to write decoupled tests.
This leads to fragile tests. If I change the builder, the consumer tests often need to change. They make it harder to get good tests in place.
That's one reason I've distanced myself from the builder pattern.
-er; Active Noun
Builders, by their very definition MUST know the inner details of another class. They are the most data envious type of systems. They WISH they were an actual object.
If you change the class, you change the builder. There's very high cohesion between the two classes... That's a code smell. Put cohesion together.
Not an Object
Builders aren't objects. They give you back an object. All they do is create the object you gave them all the details for. It's being a factory... huh... The two design patterns I think are code smells have the same fundamental behavior, create/return an object. Well... Nice to seem I'm consistent.
Builders don't represent a concept in the code. There's nothing in the domain that is a THING_Builder. It's not a concept that exists, it's a concept we force in because ... it's a pattern we know?
Hard to Remove
Builders are HARD to extract from the system. When I started MicroObjects we had a few builders. I didn't like them, but didn't have a better way. When I found a better way... wow it was hard to remove. I don't remember why, exactly. I think I've gotten better practices since then that make them easier to remove. I just know it was a pain in the ass to get rid of a few of the builders. There may still be one in the UWP app that we didn't have the time/need to remove. Deep in the foundation that we no longer touched. It's complexity/smelly that we don't see, so we don't care. :)
Not A Concept
The whole reason for this post is that the Builder Pattern is not a representation of a concept in our code. It helps create a concept that exists in the code... Except ... No. It helps create an ABSTRACT concept in our code. Not a concrete concept. Using the builder we will configure it with specific values. Those values together represent A THING. That combination IS the concept. That needs to be represented.
Hides Actual Concepts
As a stupid, simple example: A
CarBuilder can take 2 or 4 doors. We shouldn't have a concrete
Car class. We should have a
Car contract (interface or abstract, don't care) that both a
FourDoorCar adhere to. This is the concept that exists in our system.
Clearly we normally have more complex situations, but ... not really. I argue that most of your use of builders could be refactored into a new object to represent the concept the builder is helping create. The builder creates an abstract concept and makes it simple for us to not see the ACTUAL CONCEPT that exists in the code. That concept that we should make into it's own representation.
This is just some quick rambling. It ties into the idea of Representing the Concept. Builder make it harder to Represent the Concept; they force us to stay at the abstract level. We need to find the concrete concept, and represent.