Comments (16)
If you don't mind me asking, how many articles do you intend to publish on this topic?
I've one more queued up. Then you probably have some questions, and we can take if from there 😄
from ploeh.github.com.
What about Immutable Collections, i.e. https://docs.microsoft.com/en-us/dotnet/api/system.collections.immutable?view=netcore-3.1. They use builder mostly for performance reasons, that could be you refactoring approach (without the builder it is slow, with the builder it is faster to build the collections). It might be very edge case though, as you can get the builder object from the normal object.
from ploeh.github.com.
What do you think about Email
builder? The E-mail consists of several parts that have to be assembled, like recipients, subject, content, attachments, etc.
from ploeh.github.com.
Thank you, everyone, for your suggestions. A few responses to some of them:
@jalbrzym, an EmailBuilder
is an easy-to-understand example, but I hardly feel that it seems warranted. How does a Builder improve on a design where you have an Email
class with properties you can set or leave empty as needed?
@dannyfhalpotia, the Test Data Builder pattern is the example I already have on hand. You couldn't know that, because I never wrote that 😄
@bender2k14, a FileSystemPathBuilder
isn't a bad suggestion. I think that I'll go with another example, but of the ones so far suggested, I like this one best 👍
In case you're wondering...
For anyone interested, I'll briefly explain why I don't want to use the FileSystemPathBuilder
example: When writing a technical article, I need to consider how the 'average' reader will read it. When it comes to example code, the example must be complicated enough that the reader feels that what the article tries to explain is warranted. On the other hand, it can't be too complicated.
Likewise, I need to pick an example domain that's easy to grasp, but not too familiar to the normal reader. I usually need to simplify things to keep my examples easy to understand, and that's okay for most readers - as long as they aren't experts in that particular field. That's why I love the online restaurant reservation scenario so much. Most people immediately understand what it's about, but few have deep insights.
My hesitation in using FileSystemPathBuilder
is that it's a domain that most programmers know a lot about. Probably more than me, because file systems never much interested me. Thus, I'm concerned that I may present some example code that readers will find stupid or naive. This could prevent them from getting the point about the Builder pattern.
Ironically, the example that I currently have in mind is an HttpRequestMessageBuilder
. You may say that many readers will also know how to create HTTP requests, but contrary to file systems, I've worked a lot in this space.
from ploeh.github.com.
@bender2k14 yeah, the builder pattern replaces the with
expression in the first example, however the second example is a bit more nuanced. In the second example, there is no default foo
, bar
, baz
, but the builder pattern helps separate potentially complex logic when initializing these fields.
from ploeh.github.com.
Those are good links @jaco0646. Thanks for sharing :)
In a previous conversation about builders (that started with this comment), I shared this link in this comment, which is very similar to the links you have shared.
from ploeh.github.com.
I think the Ninject fluent API is a good example. The generic or ASP.net core hosting model also uses them.
from ploeh.github.com.
Cleaner examples are the
UriBuilder
andConnnectionStringBuilder
classes. These, on the other hand, I have to rule out because I'd like to include some refactorings in my article, and I can't easily refactor a published class.
You could use these examples if you selected a subset of the API that you care about and then provide your own implementations of the API. As I recall, you do something similar in your code examples with ASP.NET (it looks like you are using ASP.NET types in your controller actions because the names match, but you have little to no logic behind those types).
A similar but simpler case than "UriBuilder
" is "FileSystemPathBuilder
". The API could potentially express root paths, directory paths, file paths, absolute paths, relative paths. A simple but compelling example (in my opinion) is "building" an absolute file path by starting with a root path, "adding" some number of directory names, and then adding a file name.
Nearly every project I have worked on has expressed file system paths with strings. I would love to use stronger types, but I have never found anything that I liked enough to justify making the switch. The best I found is is contained in the NDepend.Path
namespace. See their documentation and source code.
Another thing I find very interesting about file system IO is properly separating the code according to its "concern".
- Since I have never used a strong type to represent paths, some of the code is logic and data representation (via string) for creating paths of various kinds (file vs directory and absolute vs relative) and checking if they are valid. This code can be complicated because it might depend on the operating system (Windows vs Unix) and while maybe it could be implemented purely in theory, it often seems easier to do so impurely (i.e. to check if a directory name is valid, just try to create a directory by that name). In particular, any logic here is not "business logic".
- Some of the code is impure behavior (such as getting all the names of the directories in a given directory).
- Some of the code is "business logic" (such as checking for the existence of a directory name matching a certain pattern given a sequence of directory names).
from ploeh.github.com.
How about for creating test data in unit tests? So e.g. for building a Person object you would go new PersonBuilder.Build();
and it would build a Person object with "default" properties (specified in the ctor). Then you expose each of the Person object's properties as "With" methods so you could go new PersonBuilder().WithFirstName("Danny").Build();
and then you will have a Person object with first name Danny. I've found the builder pattern to be really useful in these cases when I need to build an object quickly many times and can quickly tweak one property without having to set all the others again.
Something similar to what is being described here: https://medium.com/@arleypadua/builder-pattern-applied-to-testing-60e009c427c6
For complex properties you could also use the builder pattern e.g.
new PersonBuilder()
.WithAge(28)
.WithAddress(new AddressBuilder().WithZipCode(1159).Build())
.Build();
from ploeh.github.com.
In chapter 2 of Effective Java, Josh Bloch builds, "the Nutrition Facts label that appears on packaged foods." Regarding the pattern he uses, Bloch says, "It is a form of the Builder pattern [Gamma95, p. 97]."
I think Bloch's pattern may be more popular, because it appears far simpler than the GoF version. As an example, one may wonder why the Builder pattern needs a Director.
from ploeh.github.com.
Most of the time I'm using builder pattern for configuration classes. I like to use immutable objects, which is important with configuration classes because you don't want it to be changed in the middle of the application's execution and cause unpredictable results. Unfortunately, configurations can have a looot of properties and initializing them all via constructor as a library consumer can be annoying, especially if you just want to set one of them and leave the rest as defaults. Builder pattern solves that.
Here's an example: https://github.com/Tyrrrz/CliFx/blob/master/CliFx/CliApplicationBuilder.cs
from ploeh.github.com.
Another example is when you're writing an API that returns a contract, which also happens to be an immutable object. Let's say that object has 3 fields, but you can't retrieve the corresponding data for all of them at once. For example, you may need to make 1 HTTP request to get the first two fields and another HTTP request to get the third. That, and the fact that the returned object is immutable, means that you can't really split those two requests in separate methods. You can of course use "temporary" objects to hold the data or tuples, which may be a viable way but I find that in C# it's always easier to use the builder pattern.
Illustration:
public class Contract
{
public string Foo { get; }
public string Bar { get; }
public string Baz { get; }
public Contract(string foo, string bar, string baz) { /* ... */ }
}
// Some API method
public Contract GetContract()
{
var response1 = MakeFirstRequest();
var foo = response1["foo"];
var bar = response1["bar"];
var response2 = MakeSecondRequest();
var baz = response2["baz"];
return new Contract(foo, bar, baz);
}
As you can see, we can't easily split these two requests out, even though it's easy to image the code getting bigger and more chaotic as the data extraction gets more complicated or there are more fields to fill.
With builder pattern we can do:
public ContractBuilder FillFooBar(ContractBuilder builder)
{
var response = MakeFirstRequest();
return builder.SetFoo(response["foo"]).SetBar(response["bar"]);
}
public ContractBuilder FillBaz(ContractBuilder builder)
{
var response = MakeSecondRequest();
return builder.SetBaz(response["baz"]);
}
public Contract GetContract()
{
var builder = new ContractBuilder();
FillFooBar(builder);
FillBaz(builder);
return builder.Build();
}
Sorry for the convoluted example but I hope it gets the point across.
from ploeh.github.com.
My impression is that the case of "defaults exist for all fields/properties" is well understood. In the "base case", this is "just" F#'s record-style copy and update expression that uses the common keyword with
. In the "recursive" case, in which some field/property is also a "record", then functional lens is the standard answer (though I have never used a lens yet).
The interesting case to me is when some field/properties/values are required but there is no (reasonable) default with which to start.
I am very excited to see what Mark has to say on this topic of builder patterns.
from ploeh.github.com.
Once again thank you, all, for your suggestions. I've now written the articles that I intended to write, so I'm going to close this issue.
Feel free to continue the discussions, if you wish 😄
from ploeh.github.com.
...I've now written the articles that I intended to write...
If you don't mind me asking, how many articles do you intend to publish on this topic? (I am interested to know when the content on this topic is "over".)
from ploeh.github.com.
Reading the comparison of Builder flavors in the article reminded me of another flavor, sometimes called Builder with a twist or Step Builder.
Popular advice for a builder with required parameters is to put those in a constructor; but with more than a handful of required parameters, we return to the original problem: too much complexity in a constructor.
from ploeh.github.com.
Related Issues (20)
- [Suggestion] GitHub comments in place of Pull Requests HOT 3
- Type in today's blog post? HOT 1
- December 31, not January... ;) HOT 1
- Forward links to impureim sandwich HOT 1
- Commenting HOT 1
- [Translation] a post translation HOT 5
- 'At the boundaries, applications aren't functional' - just a question HOT 2
- What of streams? HOT 2
- suggestion: use utterances for github-based comments HOT 2
- Please make it easier to write comments HOT 6
- Subscribe to the blog? HOT 8
- Comment on "Use Git tactically" on the StackOverflow blog HOT 4
- Auto-generate values with Hedgehog HOT 5
- Dark mode HOT 3
- Clickable anchors for comments HOT 5
- Semantic section markup HOT 1
- There seems to be a problem in this article (Kleisli composition) HOT 5
- Infinite Enumerables in C# HOT 3
- Service compatibility is determined based on policy HOT 1
- re "Fitting a polynomial to a set of points" - why you "failed to accomplish what I set out to do"
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ploeh.github.com.