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

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

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

例如,在Builder内部使用Foo类而不是每个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. 此示例说明了使用Builder构建的具有不同属性的三个不同FaceBookUsers

    1. fbUser1仅作为FaceBookUser构建,仅具有userName属性
    2. fbUser2使用userName和UserBasicInfo构建为FaceBookUser
    3. fbUser3使用userName,UserBasicInfo和ContactInfo构建为FaceBookUser

在此示例中,已使用合成而不是在Builder类中复制FaceBookUser的所有属性。

编辑:

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

为简单起见,我添加了两个类:UserBasicInfo和ContactInfo。 现在使用其他属性来爆炸这个FaceBookUser类

 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(); 

您可以确保将Builder类的字段更改为私有 – 然后您只需要为构建器上的每个“属性”使用(公共)getter方法; 并且Foo中的构造函数调用这些方法; 而不是仅仅获取Builder对象中的字段。

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

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

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

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

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

问题是应用“模式”应该实现什么。 在您的问题和示例中,您已经混合了两个概念,即构建器模式和流畅的接口 。 扮演魔鬼的拥护者,人们甚至可以偷偷地说,你的案例中的“建造者”只是托马斯已经提到的参数对象 ,它以特殊的方式(流利地)构建,并且充满了publicprivate可见性的一些棘手组合。

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

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

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

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

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

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

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

     public class Person { ... } 

     public class PersonBuilder { ... } 

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

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

作为构建器模式的替代方法,您还可以使用参数对象 :

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