从实际场景来看设计模式1:Builder构建器模式

问题摘要

本文从代码编写中遇见的常见问题:如何优雅的设计需要大量参数的类?来由浅入深的学习构建器设计模式。

引经据典

遇到多个构造器参数时要考虑用构建器。

——《Effective Java》第二版

实际场景

设计一个表示食品营养成分的类,该类有必要的参数:每份含量、每份所含卡路里,以及其他非必须的参数:总脂肪量、饱和脂肪量、胆固醇及钠的含量。

在设计类时因为参数众多,所以有编码经验的同学可以立即想到使用重叠的构造函数来设计:先写一个包含必须参数的构造函数,然后写重载函数幷包含一个非必须参数并调用包含必须参数的构造函数,然后再写一个包含两个非必须参数的构造函数…

这样就得到了一个可以在创建时根据需要传入非必须参数的类,优点是思路清晰,缺点也很明显,如果这个类包含20个、30个参数时,将极大的增加编码量,而且在客户端实例化时,也并非可以简洁清晰的创建所需的类。

结论:重叠的构造器可行,但是当参数很多时,客户端代码会很难编写,并且难以阅读。

尝试使用JavaBean模式 1

听起来高大上,其实就是通过无参构造器实例化之后按需调用setter来设置属性值。

这种方式优点是比较直观,无论是类的编写还是客户端实例化都可以很方便的实现,但是带来很严重的缺陷就是,创建的类会处于中间状态而带来安全问题,当使用对象时,无法确保这个对象的参数是否全部设置好了。

在构建的过程中JavaBean可能处于不一致的状态。 类无法通过检验构造器参数来保证一致性,在高并发场景下,如果使用了处于不一致状态的对象,会带来运行结果的错误,而且无法重新和排查。

构建器模式

通过上面的解决方案我们发现,构建过程的复杂性和代码的可读性、可维护性是最大的疑难点。我们可以试想,如果将构建过程抽离出来,提供简洁的方法供使用者构建,然后返回构建的结果,如果采用这种实现方式,类的一致性和构建过程的简洁性就得到了保证。

既能保证像重叠构造器模式的安全性,又能保证JavaBean模式的可读性,这就是Builder模式

不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端通过类似setter的调用将可选参数设置进去,最后通过build来生成最终对象。

我们先直接来看实现后的实例化过程

 NutritionFacts cocacola = new NurtritionFacts.Builder(240,16)
 											.calories(25)
 											.carbohydrate(27)
 											.build();

整个代码简洁清爽,这就是设计模式的魅力。

代码实现

/**
 * @Author SunHongmin
 * @Description 当一个类实例化过程中需要许多参数,如果使用重载的构造方法来实现,客户端代码将很难直观的编写
 * 使用构建者模式将接收参数过程交由静态内部类来实现,最后返回实例成功的最终类,既保证了简洁易用,又可以避免安全隐患
 * <p>
 * 静态内部类的好处也显而易见  外部类可以直接调用内部类的私有成员并且 内部类可以使用外部类的私有构造方法
 * @Date 2020/5/17 21:40
 */
public class Builder {
    public static void main(String[] args) {
        Person person = new Person.Builder("张三", "201120")
                .age(15)
                .height(178.5)
                .weight(80)
                .build();

        System.out.println(person);
    }
}

// 实际需要实例化的类,私有化构造参数,不直接去实例化
class Person {
    private String name;
    private String sno;

    private int age;
    private double height;
    private double weight;

    private Person(Builder builder) {
        this.name = builder.name;
        this.sno = builder.sno;
        this.age = builder.age;
        this.height = builder.height;
        this.weight = builder.weight;
    }

    // 利用静态内部类来提供定制化参数设置  统一实例化入口
    public static class Builder {
        private String name;
        private String sno;

        private int age;
        private double height;
        private double weight;

        // 必须参数使用构造参数传入
        public Builder(String name, String sno) {
            this.name = name;
            this.sno = sno;
        }

        // 非必须参数提供公有方法单独设置
        public Builder age(int age) {
            this.age = age;
            return this;
        }

        public Builder height(double height) {
            this.height = height;
            return this;
        }

        public Builder weight(double weight) {
            this.weight = weight;
            return this;
        }

        // build 方法实例化person方法
        public Person build() {
            return new Person(this);
        }
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", sno='" + sno + '\'' +
                ", age=" + age +
                ", height=" + height +
                ", weight=" + weight +
                '}';
    }
}

实际应用中的注意事项

如果类的构造器或者静态工厂中需要多个参数(通常4个及以上可认为参数比较多),设计这种类时,构建器模式是一种不错的选择。

Builder构建器模式也存在不足,为了创建对象先要创建它的构造器,如果十分注重性能,可能会带来影响。

构建器模式的代码较重叠构造器更加冗长,因此只有当需要很多参数时才使用。

注意:如果类最初设计使用重叠构造器或者静态工厂实现,那么等到类需要多个参数时才添加构造器,将会无法控制,那些过时的构造器或者静态工厂将会显得不协调,因此最好一开始就使用构造器模式。


  1. 注意区分 Builder生成器模式: 生成器模式主要用于构建复杂对象 ↩︎

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章