Keeping builder in separate class (fluent interface)
Asked Answered
L

4

4
Foo foo = Foo.builder()
    .setColor(red)
    .setName("Fred")
    .setSize(42)
    .build();

So I know there is the following "Builder" solution for creating named parameters when calling a method. Although, this only seems to work with inner static classes as the builder or am I wrong? I had a look at some tutorials for builder pattern but they seem really complex for what im trying to do. Is there any way to keep the Foo class and Builder class separate while having the benefit of named parameters like the code above?

Below a typical setup:

public class Foo {
    public static class Builder {
        public Foo build() {
            return new Foo(this);
        }

        public Builder setSize(int size) {
            this.size = size;
            return this;
        }

        public Builder setColor(Color color) {
            this.color = color;
            return this;
        }

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        // you can set defaults for these here
        private int size;
        private Color color;
        private String name;
    }

    public static Builder builder() {
        return new Builder();
    }

    private Foo(Builder builder) {
        size = builder.size;
        color = builder.color;
        name = builder.name;
    }

    private final int size;
    private final Color color;
    private final String name;
}
Legalize answered 26/9, 2016 at 17:18 Comment(5)
If you're averse to the inner class, you could put both as outer classes into a package, and make the Foo constructor and Builder fields package-private.Floyd
@Floyd sounds pretty ugly right?Legalize
Yes, but then again, I don't see what's wrong with the approach you already have. Basically the Builder is a constructor on steroids, and it's not weird for the constructor to live in the same file as the class it constructs.Floyd
@Floyd My mentor / superior told me to better keep it seperate. Im currently in an internship. So im pretty unsure. In any case this is the only way or is there another one? I dont mind if I need to manually instantiate the classLegalize
This is give me a different result or would it allow me to enter the parameters the in the same fashion as shown above? tutorialspoint.com/design_pattern/builder_pattern.htmLegalize
I
4

Use composition. To make things easier and cleaner, do not replicate all attributes in source (Foo) and builder (Builder) class.

For example, have Foo class inside Builder instead of each of Foo attribute.

simple code snippet:

import java.util.*;

class UserBasicInfo{
    String nickName;
    String birthDate;
    String gender;

    public UserBasicInfo(String name,String date,String gender){
        this.nickName = name;
        this.birthDate = date;
        this.gender = gender;        
    }

    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("Name:DOB:Gender:").append(nickName).append(":").append(birthDate).append(":").
        append(gender);
        return sb.toString();
    }
}

class ContactInfo{
    String eMail;
    String mobileHome;
    String mobileWork;

    public ContactInfo(String mail, String homeNo, String mobileOff){
        this.eMail = mail;
        this.mobileHome = homeNo;
        this.mobileWork = mobileOff;
    }    
    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("email:mobile(H):mobile(W):").append(eMail).append(":").append(mobileHome).append(":").append(mobileWork);
        return sb.toString();
    }
}
class FaceBookUser {
    String userName;
    UserBasicInfo userInfo;
    ContactInfo contactInfo;

    public FaceBookUser(String uName){
        this.userName = uName;
    }    
    public void setUserBasicInfo(UserBasicInfo info){
        this.userInfo = info;
    }
    public void setContactInfo(ContactInfo info){
        this.contactInfo = info;
    }    
    public String getUserName(){
        return userName;
    }
    public UserBasicInfo getUserBasicInfo(){
        return userInfo;
    }
    public ContactInfo getContactInfo(){
        return contactInfo;
    }

    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("|User|").append(userName).append("|UserInfo|").append(userInfo).append("|ContactInfo|").append(contactInfo);
        return sb.toString();
    }

    static class FaceBookUserBuilder{
        FaceBookUser user;
        public FaceBookUserBuilder(String userName){
            this.user = new FaceBookUser(userName);
        }
        public FaceBookUserBuilder setUserBasicInfo(UserBasicInfo info){
            user.setUserBasicInfo(info);
            return this;
        }
        public FaceBookUserBuilder setContactInfo(ContactInfo info){
            user.setContactInfo(info);
            return this;
        }
        public FaceBookUser build(){
            return user;
        }
    }
}
public class BuilderPattern{
    public static void main(String args[]){
        FaceBookUser fbUser1 = new FaceBookUser.FaceBookUserBuilder("Ravindra").build(); // Mandatory parameters
        UserBasicInfo info = new UserBasicInfo("sunrise","25-May-1975","M");

        // Build User name + Optional Basic Info 
        FaceBookUser fbUser2 = new FaceBookUser.FaceBookUserBuilder("Ravindra").
                                                setUserBasicInfo(info).build();

        // Build User name + Optional Basic Info + Optional Contact Info
        ContactInfo cInfo = new ContactInfo("[email protected]","1111111111","2222222222");
        FaceBookUser fbUser3 = new FaceBookUser.FaceBookUserBuilder("Ravindra").
                                                setUserBasicInfo(info).
                                                setContactInfo(cInfo).build();

        System.out.println("Facebook user 1:"+fbUser1);
        System.out.println("Facebook user 2:"+fbUser2);
        System.out.println("Facebook user 3:"+fbUser3);
    }
}

output:

Facebook user 1:|User|Ravindra|UserInfo|null|ContactInfo|null
Facebook user 2:|User|Ravindra|UserInfo|Name:DOB:Gender:sunrise:25-May-1975:M|ContactInfo|null
Facebook user 3:|User|Ravindra|UserInfo|Name:DOB:Gender:sunrise:25-May-1975:M|ContactInfo|email:mobile(H):mobile(W):[email protected]:1111111111:2222222222

Explanation:

  1. FaceBookUser is a complex object with below attributes using composition:

    String userName;
    UserBasicInfo userInfo;
    ContactInfo contactInfo;
    
  2. FaceBookUserBuilder is a static builder class, which contains and builds FaceBookUser.

  3. userName is only Mandatory parameter to build FaceBookUser

  4. FaceBookUserBuilder builds FaceBookUser by setting optional parameters : UserBasicInfo and ContactInfo

  5. This example illustrates three different FaceBookUsers with different attributes, built from Builder.

    1. fbUser1 was built as FaceBookUser with userName attribute only
    2. fbUser2 was built as FaceBookUser with userName and UserBasicInfo
    3. fbUser3 was built as FaceBookUser with userName,UserBasicInfo and ContactInfo

In this example, composition has been used instead of duplicating all attributes of FaceBookUser in Builder class.

EDIT:

Group all related attributes into logical classes. Define all these classes in FaceBookUser. Instead of adding all these member variables again in Builder, contain FaceBookUser in Builder class.

For simplicity, I have added two classes: UserBasicInfo and ContactInfo . Now explode this FaceBookUser class with other attributes like

NewsFeed
Messages
Friends
Albums
Events
Games
Pages
Ads

etc.

If you duplicate all these attributes in both Builder and FaceBookUser, code will become difficult to manage. Instead, by using composition of FaceBookUser in FaceBookUserBuilder itself, you can simply construction process.

Once you add above attributes, you will build FaceBookUser in step-by-step process as usual.

It will be like this:

FaceBookUser fbUser3 = new FaceBookUser.FaceBookUserBuilder("Ravindra").
                                        setUserBasicInfo(info).
                                        setNewsFeed(newsFeed).
                                        setMessages(messages).
                                        setFriends(friends).
                                        setAlbums(albums).
                                        setEvents(events).
                                        setGames(games).
                                        setAds(ads).build();
Incident answered 27/9, 2016 at 1:27 Comment(6)
In that example, a FaceBookUser has a bunch of public setters. In that case, I fail to see the advantage of using a builder in the first place.Floyd
Collate related attributes to one class. That's why face book user have UserBasicInfo and ContacInfo in separate classes. Advantage of Builder using Composition here- It did not duplicate Facebook user attributes again.Incident
Each member variable of FaceBookUser can have their own builder with composition.Incident
@Ravindrababu excuse me I had trouble implementing your idea. Checked out the link too. Can you show an example on how its constructed? I mean what you explained in your edit.Legalize
If you follow the same example, you have to create classes for NewsFeed, Messages, Friends etc. Then you have to add setter methods in Builder in same way as of setUserBasicInfo e.g. setNewsFeedIncident
@Ravindrababu nevermind! I solved it. Thanks, your answer is great!Legalize
F
4

You can sure change the fields of your Builder class to be private - then you just need a (public) getter method for each "property" on the builder; and the constructor in Foo calls those methods; instead of just fetching the fields in the Builder object.

Then you can just move your Builder class out of Foo. Simple and straightforward.

But keep in mind: in the end, Builder and Foo are very closely related. They share a common set of fields by design. So any change to Foo affects Builder; and vice versa. Thus it makes a lot of sense to keep them "close together". Maybe not as inner/outer class, but maybe still within the same source file! But then ... only one of them can be public. Is that really what you want?!

In other words: don't rip things apart just "because you can". Only do it if you have good reasons to do so, and if the thing that comes out of that is better than your current solution!

Edit: your problem might not be separation of Foo and Builder, but the fact that your Foo class has too many fields in the first place. Dont forget about the single responsibility principle ... when your class needs more than 5, 6 fields ... it is probably doing too much and should be further sliced! Keep in mind: good OO design is first of all about behavior; not about having 10, 20 fields within some object!

Farnsworth answered 26/9, 2016 at 17:25 Comment(3)
But what if there are many many fields. The Builder can get huge. My mentor / superior told me to keep it seperate. What would you say? Apart from that I keep on reading inner classes are bad design so im a bit confused (as a noob). Lets say that my current Builder which im working on alone is nearly 60 lines of code.Legalize
What about tutorialspoint.com/design_pattern/builder_pattern.htm but im not sure if it allows me to enter the parameters in the same fashion.Legalize
Your mentor is correct that you should always try to have one class per file. But in the case of fluent programming in Java you simply have to make sacrifices. Whatever system you come up with will be full of generics or other limitations, like difficulty in making subclasses. I would suggest you ask your mentor for a cleaner solution I am thinking they will realize it's not so simple. In particular you want your constructors to be protected rather than public so inner class helps there.Quartziferous
I
4

Use composition. To make things easier and cleaner, do not replicate all attributes in source (Foo) and builder (Builder) class.

For example, have Foo class inside Builder instead of each of Foo attribute.

simple code snippet:

import java.util.*;

class UserBasicInfo{
    String nickName;
    String birthDate;
    String gender;

    public UserBasicInfo(String name,String date,String gender){
        this.nickName = name;
        this.birthDate = date;
        this.gender = gender;        
    }

    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("Name:DOB:Gender:").append(nickName).append(":").append(birthDate).append(":").
        append(gender);
        return sb.toString();
    }
}

class ContactInfo{
    String eMail;
    String mobileHome;
    String mobileWork;

    public ContactInfo(String mail, String homeNo, String mobileOff){
        this.eMail = mail;
        this.mobileHome = homeNo;
        this.mobileWork = mobileOff;
    }    
    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("email:mobile(H):mobile(W):").append(eMail).append(":").append(mobileHome).append(":").append(mobileWork);
        return sb.toString();
    }
}
class FaceBookUser {
    String userName;
    UserBasicInfo userInfo;
    ContactInfo contactInfo;

    public FaceBookUser(String uName){
        this.userName = uName;
    }    
    public void setUserBasicInfo(UserBasicInfo info){
        this.userInfo = info;
    }
    public void setContactInfo(ContactInfo info){
        this.contactInfo = info;
    }    
    public String getUserName(){
        return userName;
    }
    public UserBasicInfo getUserBasicInfo(){
        return userInfo;
    }
    public ContactInfo getContactInfo(){
        return contactInfo;
    }

    public String toString(){
        StringBuilder sb = new StringBuilder();
        sb.append("|User|").append(userName).append("|UserInfo|").append(userInfo).append("|ContactInfo|").append(contactInfo);
        return sb.toString();
    }

    static class FaceBookUserBuilder{
        FaceBookUser user;
        public FaceBookUserBuilder(String userName){
            this.user = new FaceBookUser(userName);
        }
        public FaceBookUserBuilder setUserBasicInfo(UserBasicInfo info){
            user.setUserBasicInfo(info);
            return this;
        }
        public FaceBookUserBuilder setContactInfo(ContactInfo info){
            user.setContactInfo(info);
            return this;
        }
        public FaceBookUser build(){
            return user;
        }
    }
}
public class BuilderPattern{
    public static void main(String args[]){
        FaceBookUser fbUser1 = new FaceBookUser.FaceBookUserBuilder("Ravindra").build(); // Mandatory parameters
        UserBasicInfo info = new UserBasicInfo("sunrise","25-May-1975","M");

        // Build User name + Optional Basic Info 
        FaceBookUser fbUser2 = new FaceBookUser.FaceBookUserBuilder("Ravindra").
                                                setUserBasicInfo(info).build();

        // Build User name + Optional Basic Info + Optional Contact Info
        ContactInfo cInfo = new ContactInfo("[email protected]","1111111111","2222222222");
        FaceBookUser fbUser3 = new FaceBookUser.FaceBookUserBuilder("Ravindra").
                                                setUserBasicInfo(info).
                                                setContactInfo(cInfo).build();

        System.out.println("Facebook user 1:"+fbUser1);
        System.out.println("Facebook user 2:"+fbUser2);
        System.out.println("Facebook user 3:"+fbUser3);
    }
}

output:

Facebook user 1:|User|Ravindra|UserInfo|null|ContactInfo|null
Facebook user 2:|User|Ravindra|UserInfo|Name:DOB:Gender:sunrise:25-May-1975:M|ContactInfo|null
Facebook user 3:|User|Ravindra|UserInfo|Name:DOB:Gender:sunrise:25-May-1975:M|ContactInfo|email:mobile(H):mobile(W):[email protected]:1111111111:2222222222

Explanation:

  1. FaceBookUser is a complex object with below attributes using composition:

    String userName;
    UserBasicInfo userInfo;
    ContactInfo contactInfo;
    
  2. FaceBookUserBuilder is a static builder class, which contains and builds FaceBookUser.

  3. userName is only Mandatory parameter to build FaceBookUser

  4. FaceBookUserBuilder builds FaceBookUser by setting optional parameters : UserBasicInfo and ContactInfo

  5. This example illustrates three different FaceBookUsers with different attributes, built from Builder.

    1. fbUser1 was built as FaceBookUser with userName attribute only
    2. fbUser2 was built as FaceBookUser with userName and UserBasicInfo
    3. fbUser3 was built as FaceBookUser with userName,UserBasicInfo and ContactInfo

In this example, composition has been used instead of duplicating all attributes of FaceBookUser in Builder class.

EDIT:

Group all related attributes into logical classes. Define all these classes in FaceBookUser. Instead of adding all these member variables again in Builder, contain FaceBookUser in Builder class.

For simplicity, I have added two classes: UserBasicInfo and ContactInfo . Now explode this FaceBookUser class with other attributes like

NewsFeed
Messages
Friends
Albums
Events
Games
Pages
Ads

etc.

If you duplicate all these attributes in both Builder and FaceBookUser, code will become difficult to manage. Instead, by using composition of FaceBookUser in FaceBookUserBuilder itself, you can simply construction process.

Once you add above attributes, you will build FaceBookUser in step-by-step process as usual.

It will be like this:

FaceBookUser fbUser3 = new FaceBookUser.FaceBookUserBuilder("Ravindra").
                                        setUserBasicInfo(info).
                                        setNewsFeed(newsFeed).
                                        setMessages(messages).
                                        setFriends(friends).
                                        setAlbums(albums).
                                        setEvents(events).
                                        setGames(games).
                                        setAds(ads).build();
Incident answered 27/9, 2016 at 1:27 Comment(6)
In that example, a FaceBookUser has a bunch of public setters. In that case, I fail to see the advantage of using a builder in the first place.Floyd
Collate related attributes to one class. That's why face book user have UserBasicInfo and ContacInfo in separate classes. Advantage of Builder using Composition here- It did not duplicate Facebook user attributes again.Incident
Each member variable of FaceBookUser can have their own builder with composition.Incident
@Ravindrababu excuse me I had trouble implementing your idea. Checked out the link too. Can you show an example on how its constructed? I mean what you explained in your edit.Legalize
If you follow the same example, you have to create classes for NewsFeed, Messages, Friends etc. Then you have to add setter methods in Builder in same way as of setUserBasicInfo e.g. setNewsFeedIncident
@Ravindrababu nevermind! I solved it. Thanks, your answer is great!Legalize
B
2

It's difficult to strictly define "The Builder Pattern™", and there are several degrees of freedom regarding the design choices. Some concepts can easily be mixed or abused, and beyond that, it is generally hard (and nearly always wrong) to say "you always have to do it exactly like that".

The question is what should be achieved by applying a "pattern". In your question and the example, you already mixed two concepts, namely the builder pattern and the fluent interface. Playing devil's advocate, one could even sneakily argue that the "Builder" in your case is just the Parameter Object that Thomas already mentioned, which is constructed in a special way (fluently) and enriched with some tricky combination of public and private visibilities.

Some of the possible goals of the builder pattern are overlapping or go hand in hand. But you should ask yourself what the primary goal is in your case:

  • Should the resulting object be immutable?
    • Should it be really immutable, with only final final fields, or could there also be setters that just should not be public? (The builder could still call these non-public setters!)
  • Is the goal to limit visibility in general?
  • Should there be polymorphic instantiation?
  • Is the main goal to summarize a large number of constructor parameters?
  • Is the main goal to offer an easy configuration with a fluent interface, and to manage "default" values? ...

As all these question will have an effect on the subtle differences in the design. However, regarding your actual, high level, "syntactic" question:

  • You could design the builder as a public static inner class (what you did in the example).

    public class Person { 
        ...
        public static PersonBuilder create() { ... }
    
        public static class PersonBuilder { 
            ...
            public Person build() { ... }
        }
    }
    

    This offers the strictest form of privacy: The constructors of Person and PersonBuilder may both be private.

  • You could also place the actual class and its builder in separate files:

    public class Person { 
        ...
    }
    

    and

    public class PersonBuilder { 
        ...
    }
    

    A reasonable degree of privacy can be achieved here: The constructors of both can be package private (i.e. have default visibility).

In both cases, the actual usage for clients would be the same, except for the name of the builder class (package.Person.PersonBuilder vs. package.PersonBuilder). The "contents" of the classes would also be the same (except for slightly different visibilities). And in both cases, you can create subclasses of Person, if desired, depending on the builder configuration, and the builder itself can have a fluent interface.

Barny answered 26/9, 2016 at 19:9 Comment(0)
F
1

As an alternative to the builder pattern, you could also use a parameter object:

class FooParams {
  public int size;
  public Color color;
  public String name;
}

You can use getters and setters here, instead of public fields, if you prefer.

Then the Foo constructor takes one of these as an argument:

public Foo(FooParams params) {
  this.size = params.size;
  this.color = params.color;
  this.name = params.name;
}
Floyd answered 26/9, 2016 at 17:50 Comment(4)
Parameter Object?Conjunctivitis
Thanks, added a link.Floyd
just a further note: instead of an own class object you might also use a simple key-value map or properties object to pass values to the constructor (or method). I.e. Apache Camel uses a simple Map<String, Object> data structure to pass so called header values within the exchange from bean to bean (at least you can inject them and reuse them within the beans).Conjunctivitis
@RomanVottner If your keys can indeed be arbitrary strings, that makes sense. But in this case, you would lose type safety and typo checking, as well as the fluent interface that a Builder has (because Map#put doesn't return this).Floyd

© 2022 - 2024 — McMap. All rights reserved.