将构建器保持在单独的 class 中(流畅的界面)

Keeping builder in separate class (fluent interface)

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

所以我知道有以下 "Builder" 解决方案,用于在调用方法时创建命名参数。虽然,这似乎只适用于作为构建器的 inner static classes 还是我错了?我看过一些关于构建器模式的教程,但它们对于我想要做的事情来说似乎真的很复杂。有没有什么办法可以让 Foo class 和 Builder class 分开,同时享受像上面的代码这样的命名参数的好处?

低于典型设置:

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;
}

您可以确保将构建器 class 的字段更改为私有 - 然后您只需要一个 (public) getter 方法用于每个 "property"建设者; Foo 中的构造函数调用这些方法;而不仅仅是获取 Builder 对象中的字段。

然后您可以将您的 Builder class 移出 Foo。简单明了。

但是请记住:最后,Builder 和 Foo 的关系非常密切。它们按设计共享一组 公共 字段。因此,对 Foo 的任何更改都会影响 Builder;反之亦然。因此,保留它们 "close together" 很有意义。可能不像 inner/outer class,但可能仍在同一个源文件中!但是然后……只有一个可以是public。这真的是你想要的吗?!

换句话说:不要只是 "because you can" 撕裂东西。只有当你有充分的理由这样做时才这样做,并且如果这样做的结果比你当前的解决方案更好

编辑:您的问题可能不是 Foo 和 Builder 的分离,而是您的 Foo class 首先有太多字段的事实。不要忘记单一职责原则……当您的 class 需要超过 5、6 个字段时……它可能做得太多了,应该进一步切分!请记住:好的 OO 设计首先是关于 行为 ;而不是在某个对象中有 10、20 个字段!

作为构建器模式的替代方案,您还可以使用 parameter object:

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

如果愿意,您可以在此处使用 getter 和 setter,而不是 public 个字段。

然后 Foo 构造函数将其中之一作为参数:

public Foo(FooParams params) {
  this.size = params.size;
  this.color = params.color;
  this.name = params.name;
}

很难严格定义“The Builder Pattern™”,并且在设计选择方面有几个自由度。有些概念很容易被混合或滥用,除此之外,通常很难(而且几乎总是错误的)说 "you always have to do it exactly like that"。

问题是应用 "pattern" 应该实现什么。在您的问题和示例中,您已经混合了两个概念,即 Thomas 已经提到的 builder pattern and the fluent interface. Playing devil's advocate, one could even sneakily argue that the "Builder" in your case is just the ,它以一种特殊的方式(流畅地)构建,并通过 public 和 [= 的一些巧妙组合进行了丰富14=] 可见性。

构建器模式的一些可能目标是重叠的或齐头并进的。但是你应该问问自己,你的主要目标是什么:

  • 生成的对象应该是不可变的吗?
    • 它应该是真的不可变的,只有final个最终字段,还是可以有不应该是public的setter? (构建器仍然可以调用这些非 public 设置器!)
  • 总的来说目标是限制能见度吗?
  • 是否应该多态实例化?
  • 主要目的是总结大量的构造函数参数吗?
  • 主要目标是提供具有流畅界面的简单配置并管理 "default" 值吗? ...

因为所有这些问题都会对设计中的细微差别产生影响。但是,关于你的实际水平,"syntactic"问题:

  • 您可以将构建器设计为 public static 内部 class(您在示例中所做的)。

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

    这提供了最严格的隐私形式:PersonPersonBuilder 的构造函数可能都是 private

  • 您也可以将实际的 class 及其生成器放在单独的文件中:

    public class Person { 
        ...
    }
    

    public class PersonBuilder { 
        ...
    }
    

    这里可以达到合理程度的隐私:两者的构造函数都可以是package private(即默认可见)。

在这两种情况下,客户端的实际用法是相同的,除了构建器的名称 class(package.Person.PersonBuilderpackage.PersonBuilder)。 classes 的 "contents" 也将是相同的(除了可见性略有不同)。在这两种情况下,如果需要,您可以创建 Person 的子 class,具体取决于构建器配置,并且构建器本身可以具有流畅的界面。

使用构图。为了使事情变得更简单和更清晰,不要复制源 (Foo) 和构建器 (Builder) class.

中的所有属性

例如,在 Builder 中有 Foo class 而不是每个 Foo 属性。

简单的代码片段:

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("xxx@xyz.com","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);
    }
}

输出:

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):xxx@xyz.com:1111111111:2222222222

解释:

  1. FaceBookUser 是一个复杂对象,使用组合具有以下属性:

    String userName;
    UserBasicInfo userInfo;
    ContactInfo contactInfo;
    
  2. FaceBookUserBuilder 是一个静态构建器 class,它包含并构建 FaceBookUser.

  3. userName 只是构建 FaceBookUser 的必需参数

  4. FaceBookUserBuilder 通过设置可选参数构建 FaceBookUserUserBasicInfoContactInfo

  5. 此示例说明了三个具有不同属性的不同 FaceBookUsers,它们是从 Builder 构建的。

    1. fbUser1 被构建为仅具有 userName 属性的 FaceBookUser
    2. fbUser2 被构建为 FaceBookUser,具有 userName 和 UserBasicInfo
    3. fbUser3 被构建为 FaceBookUser,具有 userName、UserBasicInfo 和 ContactInfo

在这个例子中,使用了组合而不是复制 Builder 中 FaceBookUser 的所有属性 class。

编辑:

将所有相关属性分组为逻辑 classes。在 FaceBookUser 中定义所有这些 classes。不要在 Builder 中再次添加所有这些成员变量,而是在 Builder class 中包含 FaceBookUser

为简单起见,我添加了两个 classes:UserBasicInfo 和 ContactInfo。现在用

等其他属性分解此 FaceBookUser class
NewsFeed
Messages
Friends
Albums
Events
Games
Pages
Ads

等等

如果在 BuilderFaceBookUser 中复制所有这些属性,代码将变得难以管理。相反,通过在 FaceBookUserBuilder 本身中使用 FaceBookUser 的组合,您可以简单地构建过程。

添加以上属性后,您将像往常一样逐步构建 FaceBookUser

会变成这样:

FaceBookUser fbUser3 = new FaceBookUser.FaceBookUserBuilder("Ravindra").
                                        setUserBasicInfo(info).
                                        setNewsFeed(newsFeed).
                                        setMessages(messages).
                                        setFriends(friends).
                                        setAlbums(albums).
                                        setEvents(events).
                                        setGames(games).
                                        setAds(ads).build();