Take the classical example of a Pizza
.
In some restaurants, there are different variations of pizza.
One with tomatoes and tuna, the other with tomatoes, tuna AND cheese, another with tomatoes, tuna, cheese and ONE egg in the middle.
It's a complex object in the sense that it potentially allows a huge combination of fields / ingredients.
So regarding a same object, here the pizza, there are several possible representations (variations).
Without a practical way of constructing this object, you would deal with a lot of possible constructors dealing with each variation, a real mess in the Pizza
class.
You evoked the fact to get rid of those constructors by using some setters.
But in a good OO design, you want to get instance of a complete object at once through one constructor or ...another fluent way that consists of the ... builder pattern to avoid creating an unfortunate partial Pizza before finishing adding its REQUIRED ingredients.
Besides, setters potentially break several invariants that should be protect by the object itself.
Indeed, what if a code client creates a "Neptune Pizza" while forgetting to add the main ingredient like tuna, it would not be a (valid) Pizza any more.
Simple setters give "power of manipulation" to any client, that shouldn't get it.
While builder object is also responsible to validate the required fields while allowing to omit optional fields.
Builder pattern aims to avoid those "infinite" additions of constructors to represent each pizza by the way of a fluent API that allows easy, incremental and cohesive combination of Pizza's ingredients and THEN returning the consistent and complete object to the client.