4 ms·
Where I've really seen builders be useful is when you have lots and lots of parameters, where most of the time defaults are fine, and you may actually want to p
by asimpletune 5y ago
Where I've really seen builders be useful is when you have lots and lots of parameters, where most of the time defaults are fine, and you may actually want to pass then around to other parts of the systems, perhaps with different dependencies, who will then further modify them.
An example of this would be configuring a client. Your unit tests may want the client to literally just be a function that returns "OK", your integration tests may want the client dynamically take different parameters depending on what part of the system is being tested (but leave the rest the same), and in production it may need to go through some kind of proxy that your company has.
If you just have one method to construct these things, in each of those many, many different places, you have these huge long constructors being called. It becomes difficult to audit, so mistakes can be made as the requirements evolve, but also, even worse, some chap always comes along and refactors the whole system to optimize for "code reuse", but instead makes a single chokepoint where now one change in one part of the system necessitates breaking every other part of the system. The problem is you don't actually experience that until later down the road, but by that point said chap from before has already been touted as a thought leader in the organization, etc...
Builders are really good in these cases. Especially since you can pass the builder objects along and modify them along the way, and then inject them as dependencies where needed. You can also add special typing if you need to provide certain guard rails, I.e. 'AuthorizedClient' once you've supplied username + password, or whatever.
- whimsicalism 5y agoWhy is this better than a configurable config object that you pass around? I'm downvoted but I am legitimately curious.
- asimpletune 5y agoI don't think it's a bad question, and it's not like there's a right or wrong answer to any of it. To me, the reason why in the example I gave a builder is better than a config object is because objects necessitate all their fields to be set upfront. You could then say, "Well why don't you just have getters and setters for all of those fields?". And it's like, ok sure, we could do that, but what if something else is using that object and we're mutating it? So then you could say, "well what if the setters return a *copy* of the object, the you can then pass along?" And then it's like, ok, well now we basically have a builder, we're just calling it different things.
- kragen 5y ago> what if something else is using that object and we're mutating it? You're probably aware that in Rust we have other, better safeguards against that, and you were probably talking about languages like Java and C++ where you don't, but I thought I'd mention it in case someone reading the thread doesn't know that.
- asimpletune 5y agoYeah totally, I just meant as a pattern. Nonetheless, in rust even though you may have the borrow checker to safeguard against the mistake, that still leaves you with now needing to accomplish the thing, in spite of the borrow checker. Which is where using a builder would be one way to do it. So even rust you still need *something*, it’s just safer and more obvious to see why, because you literally can’t share the reference in two places.
- skohan 5y agoThe thing I really don't like about the builder pattern is that it obscures the possibility space. With named arguments/defult parameters, you know exactly what the fields are you need to consider. With builders, you can wander into an unfamiliar codebase, and you have to read through all the methods on this builder object to understand what's going on.
- davidatbu 5y agoDoes autocomplete (ie, pressing "." and waiting for the IDE) help with that?