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

时间:2016-09-26 17:18:43

标签: java design-patterns fluent builder-pattern

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

所以我知道在调用方法时有以下“Builder”解决方案用于创建命名参数。虽然,这似乎只适用于内部静态类作为构建器,或者我错了吗?我看了一些构建器模式的教程,但是对于我想要做的事情,它们看起来非常复杂。是否有任何方法可以保持Foo类和Builder类分离,同时享受上述代码等命名参数的好处?

典型设置下方:

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

4 个答案:

答案 0 :(得分:4)

您可以确定将Builder类的字段更改为私有 - 然后您只需要为每个"属性"提供一个(公共)getter方法。在建设者;并且Foo中的构造函数调用这些方法;而不只是获取Builder对象中的字段。

然后你可以将你的Builder类移出Foo。简单明了。

请记住:最后,Builder和Foo密切相关。他们按设计共享常用字段集。所以对Foo的任何改变都会影响Builder;反之亦然。因此,让它们保持紧密联系是非常有意义的。也许不是内部/外部类,但可能仍然在同一个源文件中!但那时......其中只有一个可以公开。这真的是你想要的吗?!

换句话说:不要把事情分开,因为你可以"。只有在你有充分理由这样做的情况下才能做到这一点,并且如果出现这种情况更好而不是你当前的解决方案!

编辑:您的问题可能不是Foo和Builder的分离,而是您的Foo类首先包含太多字段。不要忘记单一的责任原则......当你的班级需要超过5,6个领域......它可能做得太多而且应该进一步切片!请记住:良好的OO设计首先是关于行为;不是在某个对象中有10个,20个字段!

答案 1 :(得分:2)

很难严格定义“ Builder Pattern™”,并且设计选择有几个自由度。有些概念很容易被混合或滥用,除此之外,说“你总是必须完全像那样”一般很难(而且几乎总是错误的) 。

问题是应用“模式”应该实现什么。在您的问题和示例中,您已经混合了两个概念,即builder patternfluent interface。扮演魔鬼的拥护者,甚至可以偷偷地说,在你的案例中,“建造者”只是托马斯已经提到过的Parameter Object,它是以一种特殊的方式(流利地)构建的,并且富含了{{1}的一些棘手组合。 }}和public可见性。

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

  • 结果对象应该是不可变的吗?
    • 它应该真的不可变,只有private个最终字段,还是可能还有不应该公开的setter? (建造者仍然可以称这些非公共制定者!)
  • 目标是限制一般的可见度吗?
  • 是否应该进行多态实例化?
  • 总结大量构造函数参数的主要目标是什么?
  • 主要目标是通过流畅的界面提供简单的配置,并管理“默认”值吗? ...

因为所有这些问题都会对设计中的微妙差异产生影响。但是,关于你的实际的,高级别的“句法”问题:

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

    public static

    这提供了最严格的隐私形式:public class Person { ... public static PersonBuilder create() { ... } public static class PersonBuilder { ... public Person build() { ... } } } Person的构造函数可能都是PersonBuilder

  • 您还可以将实际的类及其构建器放在单独的文件中:

    private

    public class Person { 
        ...
    }
    

    这里可以实现合理程度的隐私:两者的构造函数可以是包私有(即具有默认可见性)。

在这两种情况下,客户端的实际使用情况都是相同的,但构建器类的名称除外(public class PersonBuilder { ... } package.Person.PersonBuilder)。课程的“内容”也是相同的(除了略微不同的可见性)。在这两种情况下,您可以根据需要创建package.PersonBuilder的子类,具体取决于构建器配置,构建器本身可以具有流畅的界面。

答案 2 :(得分:1)

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

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

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

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

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

答案 3 :(得分:0)

使用构图。为了使事情更简单,更清晰,请不要复制源(Foo)和构建器(Builder)类中的所有属性。

例如,Foo内有Builder个课程,而不是每个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是一个静态构建器类,包含并构建FaceBookUser

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

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

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

    1. fbUser1构建为FaceBookUser,仅具有userName属性
    2. fbUser2使用userName和UserBasicInfo
    3. 构建为FaceBookUser
    4. fbUser3使用userName,UserBasicInfo和ContactInfo构建为FaceBookUser
  6. 在此示例中,使用了合成而不是在Builder类中复制FaceBookUser的所有属性。

    修改

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

    为简单起见,我添加了两个类:UserBasicInfo和ContactInfo。现在使用其他属性(如

    )来爆炸这个FaceBookUser类
    NewsFeed
    Messages
    Friends
    Albums
    Events
    Games
    Pages
    Ads
    

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

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

    会是这样的:

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