• Java 基础 Builder模式


    问题陈述

    在编程的时候,有时候我们遇到有些JavaBean类有很多的field,于是在构造该bean的时候我们可能要提供一个入参很长的构造方法。例如下面的Person类:

    Person.java

    public class Person
    {
       private final String lastName;
       private final String firstName;
       private final String middleName;
       private final String salutation;
       private final String suffix;
       private final String streetAddress;
       private final String city;
       private final String state;
       private final boolean isFemale;
       private final boolean isEmployed;
       private final boolean isHomewOwner;
    
       public Person(
          final String newLastName,
          final String newFirstName,
          final String newMiddleName,
          final String newSalutation,
          final String newSuffix,
          final String newStreetAddress,
          final String newCity,
          final String newState,
          final boolean newIsFemale,
          final boolean newIsEmployed,
          final boolean newIsHomeOwner)
       {
          this.lastName = newLastName;
          this.firstName = newFirstName;
          this.middleName = newMiddleName;
          this.salutation = newSalutation;
          this.suffix = newSuffix;
          this.streetAddress = newStreetAddress;
          this.city = newCity;
          this.state = newState;
          this.isFemale = newIsFemale;
          this.isEmployed = newIsEmployed;
          this.isHomewOwner = newIsHomeOwner;
       }
    }
    

    在构造Person类的时候,我们不得不new 一个带有极长参数列表的构造函数:

    Person person = new Person("Xingjian", "Long", "great", ... (省略余下数十个参数));
    

    这么写从逻辑和功能上来说没有错,但是客户端在调用的时候很容易传错参数。如果参数少传则会产生编译错误,这时还可以排查出来;但如果是顺序错了,比如在该传firstName的位置写了lastName,则程序可以正确运行,但却产生了错误的结果,这样会导致难以排查问题。编程的原则是不要隐藏和模糊问题。
    以上面的这个Person类为例,我们针对上面的问题可以做的两个方面的改进:

    使用自定义的类型来保存入参

    上面的Person类的前7个参数都是String类型,因此在构造的时候只要在这7个位置传入的是String类型的参数都可以正常构造一个Person类,即使我们在该传city的位置传入了lastName,编译器也不会报任何错误,因为这两个属性都是String类型的。为了避免这个错误,我们可以为不同的属性包装一个专属的类型。
    比如为lastName和city属性各自定义一个lastName类和city类:

    LastName.java

    public class LastName{
        private String lastName;
        //setter&&getter&&Constructor
    }
    

    City.java

    public class City{
        private String city;
        //setter&&getter&&Constructor
    }
    

    这样在构造Person类的时候,在该传city的地方必须传City类型的变量,在该传lastName的地方就必须传LastName类型的变量,如果在传参的时候把这两个变量的位置搞错,则编译就通不过,从而提前发现了错误。

    入参聚合

    这么做的缺点显而易见,就是需要自己定义很多的辅助类。对于上面的例子的一个改进是构建参数对象,将属于同一个业务范围的参数聚合为一个对象传入,如firstNamemiddlerNamelastName可以统一到一个对象name中:

    Name.java

    public class Name{
        private String firstName;
        private String middleName;
        private String lastName;
        //setter&&getter&&constructor
    }
    

    当然这里传入Name对象的三个参数也可以是自定义的FirstNameMiddleNameLastName类型的对象。
    这样可以减少在构造Person类的时候传参的个数,把一长串的参数分解到几个较小的对象中去。

    Builder模式

    Effective Java第二版中, Josh Bloch介绍了Builder模式,这个模式专门用来处理构造那些需要设置很多属性的对象的问题。

    不直接生成想要的对象,通过调用构造器(或静态工厂),得到builder对象,客户端在builder对象上调用类似setter方法,来设置每个相关的可选参数,最后,再调用build方法来生成不可变对象。

    还以上面的Person类对象为例,builder模式有两种实现方法:

    单独的Builder类

    NetBeans可以自动构建一个如下的PersonBuilder类:

    PersonBuilder.java

    public class PersonBuilder
    {
       private String newLastName;
       private String newFirstName;
       private String newMiddleName;
       private String newSalutation;
       private String newSuffix;
       private String newStreetAddress;
       private String newCity;
       private String newState;
       private boolean newIsFemale;
       private boolean newIsEmployed;
       private boolean newIsHomeOwner;
    
       public PersonBuilder()
       {
       }
    
       public PersonBuilder setNewLastName(String newLastName) {
          this.newLastName = newLastName;
          return this;
       }
    
       public PersonBuilder setNewFirstName(String newFirstName) {
          this.newFirstName = newFirstName;
          return this;
       }
    
       public PersonBuilder setNewMiddleName(String newMiddleName) {
          this.newMiddleName = newMiddleName;
          return this;
       }
    
       public PersonBuilder setNewSalutation(String newSalutation) {
          this.newSalutation = newSalutation;
          return this;
       }
    
       public PersonBuilder setNewSuffix(String newSuffix) {
          this.newSuffix = newSuffix;
          return this;
       }
    
       public PersonBuilder setNewStreetAddress(String newStreetAddress) {
          this.newStreetAddress = newStreetAddress;
          return this;
       }
    
       public PersonBuilder setNewCity(String newCity) {
          this.newCity = newCity;
          return this;
       }
    
       public PersonBuilder setNewState(String newState) {
          this.newState = newState;
          return this;
       }
    
       public PersonBuilder setNewIsFemale(boolean newIsFemale) {
          this.newIsFemale = newIsFemale;
          return this;
       }
    
       public PersonBuilder setNewIsEmployed(boolean newIsEmployed) {
          this.newIsEmployed = newIsEmployed;
          return this;
       }
    
       public PersonBuilder setNewIsHomeOwner(boolean newIsHomeOwner) {
          this.newIsHomeOwner = newIsHomeOwner;
          return this;
       }
    
       public Person createPerson() {
          return new Person(newLastName, newFirstName, newMiddleName, newSalutation, newSuffix, newStreetAddress, newCity, newState, newIsFemale, newIsEmployed, newIsHomeOwner);
       }
    }
    

    有了上面的这个Builder类,可以像下面这样使用它构建一个Person

    PersonBuilder builder = new PersonBuilder();
            builder.setNewCity("Beijing")
                    .setNewFirstName("long")
                    .setNewMiddleName("Gatsby")
                    .setNewLastName("xingjian")
                    .setNewState("China")
                    .setNewStreetAddress("Shangdixilu")
                    .setNewSuffix("great")
                    .setNewSalutation("hello")
                    .setNewIsHomeOwner(true)
                    .setNewIsEmployed(true)
                    .setNewIsFemale(false);
            Person person= builder.createPerson();
    

    这样通过builderset方法链式构造Person类,set方法也会指示每个该传入的参数,从而避免传错参数。

    静态内部Builder类

    如今比较常见的做法是在Person类中添加静态内部Builder类。修改Person类如下:

    public class Person
    {
       private final String lastName;
       private final String firstName;
       private final String middleName;
       private final String salutation;
       private final String suffix;
       private final String streetAddress;
       private final String city;
       private final String state;
       private final boolean isFemale;
       private final boolean isEmployed;
       private final boolean isHomewOwner;
    
       public Person(
          final String newLastName,
          final String newFirstName,
          final String newMiddleName,
          final String newSalutation,
          final String newSuffix,
          final String newStreetAddress,
          final String newCity,
          final String newState,
          final boolean newIsFemale,
          final boolean newIsEmployed,
          final boolean newIsHomeOwner)
       {
          this.lastName = newLastName;
          this.firstName = newFirstName;
          this.middleName = newMiddleName;
          this.salutation = newSalutation;
          this.suffix = newSuffix;
          this.streetAddress = newStreetAddress;
          this.city = newCity;
          this.state = newState;
          this.isFemale = newIsFemale;
          this.isEmployed = newIsEmployed;
          this.isHomewOwner = newIsHomeOwner;
       }
    
       public static class PersonBuilder
       {
          private String nestedLastName;
          private String nestedFirstName;
          private String nestedMiddleName;
          private String nestedSalutation;
          private String nestedSuffix;
          private String nestedStreetAddress;
          private String nestedCity;
          private String nestedState;
          private boolean nestedIsFemale;
          private boolean nestedIsEmployed;
          private boolean nestedIsHomeOwner;
    
          public PersonBuilder(
             final String newFirstName,
             final String newCity,
             final String newState) 
          {
             this.nestedFirstName = newFirstName;
             this.nestedCity = newCity;
             this.nestedState = newState;
          }
    
          public PersonBuilder lastName(String newLastName)
          {
             this.nestedLastName = newLastName;
             return this;
          }
    
          public PersonBuilder firstName(String newFirstName)
          {
             this.nestedFirstName = newFirstName;
             return this;
          }
    
          public PersonBuilder middleName(String newMiddleName)
          {
             this.nestedMiddleName = newMiddleName;
             return this;
          }
    
          public PersonBuilder salutation(String newSalutation)
          {
             this.nestedSalutation = newSalutation;
             return this;
          }
    
          public PersonBuilder suffix(String newSuffix)
          {
             this.nestedSuffix = newSuffix;
             return this;
          }
    
          public PersonBuilder streetAddress(String newStreetAddress)
          {
             this.nestedStreetAddress = newStreetAddress;
             return this;
          }
    
          public PersonBuilder city(String newCity)
          {
             this.nestedCity = newCity;
             return this;
          }
    
          public PersonBuilder state(String newState)
          {
             this.nestedState = newState;
             return this;
          }
    
          public PersonBuilder isFemale(boolean newIsFemale)
          {
             this.nestedIsFemale = newIsFemale;
             return this;
          }
    
          public PersonBuilder isEmployed(boolean newIsEmployed)
          {
             this.nestedIsEmployed = newIsEmployed;
             return this;
          }
    
          public PersonBuilder isHomeOwner(boolean newIsHomeOwner)
          {
             this.nestedIsHomeOwner = newIsHomeOwner;
             return this;
          }
    
          public Person createPerson()
          {
             return new Person(
                nestedLastName, nestedFirstName, nestedMiddleName,
                nestedSalutation, nestedSuffix,
                nestedStreetAddress, nestedCity, nestedState,
                nestedIsFemale, nestedIsEmployed, nestedIsHomeOwner);
          }
       }
    }
    

    上面的PersonBuilder类是Person类的静态内部类。构造Person对象的时候可以像下面这样:

    Person person1 = new Person.PersonBuilder("Long", "Beijing", "China")
                   .isEmployed(true)
                   .isFemale(false)
                   .isHomeOwner(true)
                   .lastName("Xingjian")
                   .middleName("Bill")
                   .streetAddress("xi'erqi")
                   .salutation("bonjour")
                   .suffix("excellent")
                   .createPerson();
    

    这个builder在使用的时候可以指定带有一些参数的构造器来使用。

    lombok 的@Builder注解

    lombok包除了可以通过@Data来帮我们生成gettersetter函数,@AllConstructor帮我们生成全参数构造函数,还可以用@Builder注解生成静态内部Builder类。只需在类上使用Builder注解:

    @Builder
    @ToString
    public class Person {
        private String name;
        private String address;
        private String firstName;
        private String lastName;
    }
    

    查看生成的class源码:

    public class Person {
        private String name;
        private String address;
        private String firstName;
        private String lastName;
    
        @ConstructorProperties({"name", "address", "firstName", "lastName"})
        Person(String name, String address, String firstName, String lastName) {
            this.name = name;
            this.address = address;
            this.firstName = firstName;
            this.lastName = lastName;
        }
    
        public static Person.PersonBuilder builder() {
            return new Person.PersonBuilder();
        }
    
        public String toString() {
            return "Person(name=" + this.name + ", address=" + this.address + ", firstName=" + this.firstName + ", lastName=" + this.lastName + ")";
        }
    
        public static class PersonBuilder {
            private String name;
            private String address;
            private String firstName;
            private String lastName;
    
            PersonBuilder() {
            }
    
            public Person.Builder name(String name) {
                this.name = name;
                return this;
            }
    
            public Person.PersonBuilder address(String address) {
                this.address = address;
                return this;
            }
    
            public Person.Builder firstName(String firstName) {
                this.firstName = firstName;
                return this;
            }
    
            public Person.Builder lastName(String lastName) {
                this.lastName = lastName;
                return this;
            }
    
            public Person build() {
                return new Person(this.name, this.address, this.firstName, this.lastName);
            }
    
            public String toString() {
                return "Person.Builder(name=" + this.name + ", address=" + this.address + ", firstName=" + this.firstName + ", lastName=" + this.lastName + ")";
            }
        }
    }
    

    客户端如下调用即可:

            Person Person = Person.builder().address("Beijing").firstName("Long").lastName("Xingjian").name("great").build();
            System.out.println(Person);
    

    参考:

    JavaBean之Builder模式
    Too Many Parameters in Java Methods, Part 3: Builder Pattern

  • 相关阅读:
    【Java小项目】一个Socket连续传输多个文件
    【Java小项目】图片浏览器
    【Java小项目】山寨QQ
    Git学习笔记
    【Java爬虫】爬取南通大学教务系统成绩计算绩点
    【Little_things】事件驱动的带界面的Client/Server聊天小程序(java socket)
    【Little_things】简单的Client/Server通信小程序(java socket)
    Codeforces Round #222 (Div. 1) (ABCDE)
    2019 牛客多校五 F. maximum clique 1 (最大团)
    Student's Camp CodeForces
  • 原文地址:https://www.cnblogs.com/greatLong/p/11969767.html
Copyright © 2020-2023  润新知