Git Product home page Git Product logo

Comments (16)

ploeh avatar ploeh commented on July 24, 2024 2

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.

Tornhoof avatar Tornhoof commented on July 24, 2024 1

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.

jalbrzym avatar jalbrzym commented on July 24, 2024 1

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.

ploeh avatar ploeh commented on July 24, 2024 1

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.

Tyrrrz avatar Tyrrrz commented on July 24, 2024 1

@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.

TysonMN avatar TysonMN commented on July 24, 2024 1

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.

idavis avatar idavis commented on July 24, 2024

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.

TysonMN avatar TysonMN commented on July 24, 2024

Cleaner examples are the UriBuilder and ConnnectionStringBuilder 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".

  1. 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".
  2. Some of the code is impure behavior (such as getting all the names of the directories in a given directory).
  3. 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.

dannyfhalpotia avatar dannyfhalpotia commented on July 24, 2024

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.

jaco0646 avatar jaco0646 commented on July 24, 2024

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.

Tyrrrz avatar Tyrrrz commented on July 24, 2024

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.

Tyrrrz avatar Tyrrrz commented on July 24, 2024

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.

TysonMN avatar TysonMN commented on July 24, 2024

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.

ploeh avatar ploeh commented on July 24, 2024

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.

TysonMN avatar TysonMN commented on July 24, 2024

...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.

jaco0646 avatar jaco0646 commented on July 24, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.