在寻找Builder模式的解释时,您可能会发现一些文章都展示了专门用于创建对象的类。 这很简单,我们可以进一步研究这种模式。

让我们从上课开始人下面:

public class Person {

   private String firstname;

   private String lastname;

   private String nickname;

   private String email;

   private String gender;

   private String country;

   private String city;

   public Person(String firstname, String lastname, String nickname, String email, String gender, String country, String city) {
       this.firstname = firstname;
       this.lastname = lastname;
       this.nickname = nickname;
       this.email = email;
       this.gender = gender;
       this.country = country;
       this.city = city;
   }
}

从这段代码中我们可以看到,创建新的Person对象时会出现问题:

  • constructor that's too big,
  • constructor parameters have all the same type. We have to remember the right order of each parameter when using it. In fact, we are façing a common code smell: primitive obsession. You can take a look at this article which explains how to recognize and deal with them.
  • How can we set up constraints? It can be difficult to check nullity or to ensure consistency between fields.

因此,为了处理其中的一些问题,我们可以添加几个构造函数。 例如:

public Person(String firstname, String lastname) {
   this(firstname, lastname, null, null, null, null, null);
}

public Person(String firstname, String lastname, String nickname) {
   this(firstname, lastname, nickname, null, null, null, null);
}

public Person(String firstname, String lastname, String nickname, String email) {
   this(firstname, lastname, nickname, email, null, null, null);
}

通过向我们的Person类添加不同的构造函数,结果是建设责任委托给客户。 想要创建对象Person的开发人员必须知道要使用哪个构造函数以及原因。 而且,生成的代码难以阅读和理解:

Person john = new Person("john", "smith", "john");

在这段简单的代码中,很难知道名字,姓氏和昵称参数在哪里。 我们必须遍历源代码以检查将初始化哪些字段。 一个方便的解决方案是创建一个构建器类来解决此可读性问题。

public class PersonBuilder {

   private final Person person;

   private PersonBuilder() {
       person = new Person();
   }

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

   public PersonBuilder withFirstname(String firstname) {
       if (firstname == null) {
           throw new IllegalArgumentException("firstname must be not null");
       }
       person.setFirstname(firstname);
       return this;
   }

   public PersonBuilder withLastname(String lastname) {
       if (lastname == null) {
           throw new IllegalArgumentException("lastname must be not null");
       }
       person.setLastname(lastname);
       return this;
   }

  ...

   public Person build() {
       return person;
   }
}

然后,我们可以像这样使用它:

Person john = PersonBuilder.builder()
   .withFirstname("john")
   .withLastname("smith")
   .build();

Great! This solution has the benefit of explicit arguments. We can easily understand what person's first name is. In addition, it was easy to add a not-null constraint for each building method. Furthermore, as each method returns the instance of PersonBuilder, it provides us a pseudo DomainSpecificLanguage.

但是,与构造器不同,此构造器不是不言自明的,并且可能会错误使用。 例如:

Person john = PersonBuilder.builder()
    .build();

最简单的情况表明缺乏指导。 实际上,可以在创建阶段指导开发人员。 例如,假设我们要将创建过程分为四个步骤:

  • 开发人员必须先设置名字,然后他可以设置姓氏然后他可以设置电子邮件最后他可以构建一个Person对象。

为此,我们必须创建四个接口:

public interface StepFirstnameBuilder {

   StepLastnamePersonBuilder withFirstname(String firstname);
}

第一步使开发人员可以设置名字字段,然后使用第二步界面StepLastnamePersonBuilder。

 public interface StepLastnamePersonBuilder {

   StepEmailPersonBuilder withLastname(String lastname);
}

public interface StepEmailPersonBuilder {

   FinalStepPersonBuilder withEmail(String email);
}


public interface FinalStepPersonBuilder {

   Person build();
}

最后,设置完所有必填字段后,我们可以访问建立方法。 而已! 现在我们可以修改人建类来实现我们的四个步骤。

public class PersonBuilder implements StepFirstnameBuilder, 
StepLastnamePersonBuilder,
StepEmailPersonBuilder,
FinalStepPersonBuilder {


   private final Person person;

   private PersonBuilder() {
       person = new Person();
   }

   public static StepFirstnameBuilder builder() {
       return new PersonBuilder();
   }

   @Override
   public StepLastnamePersonBuilder withFirstname(String firstname) {
       if (firstname == null) {
           throw new IllegalArgumentException("firstname must be not null");
       }
       person.setFirstname(firstname);
       return this;
   }

   @Override
   public StepEmailPersonBuilder withLastname(String lastname) {
       if (lastname == null) {
           throw new IllegalArgumentException("lastname must be not null");
       }
       person.setLastname(lastname);
       return this;
   }

   @Override
   public FinalStepPersonBuilder withEmail(String email) {
       person.setEmail(email);
       return this;
   }

   @Override
   public Person build() {
       return person;
   }
}

现在,让我们使用它:

Person john = PersonBuilder.builder() // -> returrns an instance of StepFirstnameBuilder
   .withFirstname("john") // -> returns an instance of StepLastnamePersonBuilder
   .withLastname("smith") // -> returns an instance of StepEmailPersonBuilder
   .withEmail("john@smith") // -> returns an instance of FinalStepPersonBuilder
   .build();

通过执行所有这些步骤,我们完全控制了Person对象的构建方式。 实际上,我们刚刚创建了DomainSpecificLanguage。 这种Builder模式是FluentInterface专门用于构建对象的一种特殊情况,它是表达对象构建方式的简便方法。

一种 big thanks to Sonyth and Mickael for their time and proofreading.

from: https://dev.to//vga/builder-pattern-a-first-step-to-dsl-47de

更多推荐

构建器模式,DSL的第一步