設計模式系列-Builder模式(高效構建參數)

引言

在Java編程中,常常需要爲一個Bean構建成員變量或者構建參數,常用的方法有使用構造函數、使用JavaBean的set()方法,但是這兩個方案或多或少都存在一定的缺點,於是今天的主角builder模式出場了,它解決了這種典型應用場景的問題,採用簡潔明瞭的使用方式,靈活多變的鏈式調用,使得多個參數的Bean的構建變得十分簡潔。

本文不想以傳統的類圖的形式來講解,而是從實際例子來看builder模式到底是什麼?如何使用?

以下使用一個Student Bean來進行舉例說明。

一、傳統的構造函數

傳統方式都是使用構造函數在構建Bean,例如存在一個學生類Student,包含id,name,age,height幾個成員變量,其中idname是必須的,ageheight是可選的。如果使用傳統的構造函數,一般情況下編碼如下

public class Student {

    private final String name;
    private final int id;
    private final int age;
    private final int height;

    public Student(String name,int id,int age, int height){
        this.name = name;
        this.id = id;
        this.age = age;
        this.height = height;
    }

    public Student(String name,int id) {
        this(name,id,0,0);
    }

    public Student(String name,int id,int age){
        this(name,id,age,0);
    }
    


    public String getName() {
        return name;
    }

    public int getId() {
        return id;
    }

    public int getAge() {
        return age;
    }

    public int getHeight() {
        return height;
    }

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

存在的問題主要有兩個

  1. 構造函數衆多,顯得十分繁瑣,如果參數繼續增加,則構造函數會更多
  2. 無法很好地區分參數,例如public Student(String name,int id,int age),可選地參數是age,但是從構造函數上很難看出來,並且由於height與age一樣都是int類型,所以不能出現public Student(String name,int id,int age),也就是沒辦法實現只傳入一個age或者一個height參數地情況,而是不得不使用public Student(String name,int id,int age, int height),即便我只是想設置一個height不想設置age,我也必須爲這個age字段設置一個默認值。

二、JavaBean setter方法

爲了解決以上問題,又一個經典地方案出現了,這就是JavaBean的setter方法,需要構造一個參數爲空的構造函數,所有的成員變量通過setter方法進行設置。

public class Student {

    private  String name;
    private  int id;
    private  int age;
    private  int height;

    public Student(){
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setId(int id) {
        this.id = id;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public String getName() {
        return name;
    }

    public int getId() {
        return id;
    }

    public int getAge() {
        return age;
    }

    public int getHeight() {
        return height;
    }

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

}

這個方法也有比較明顯的缺點,首先參數多的時候就需要調用很長的一系列setter方法,另外無法區分哪些是必須設置的成員變量,哪些是可選的成員變量,並且成員變量多的時候很容易漏掉一些setter,並且難以檢查出來,很容易造成錯誤。

三、Builder模式

使用Builder模式使得這一切變得更加簡潔,方便使用,先來看看Builder模式下如何編碼

package tech.liujintao.leetcode;

public class Student {

    private final String name;
    private final int id;
    private final int age;
    private final int height;

    private Student(StudentBuilder studentBuilder) {
        this.name=studentBuilder.name;
        this.id =studentBuilder.id;
        this.age = studentBuilder.age;
        this.height = studentBuilder.height;
    }

    public static class StudentBuilder
    {
        private String name;
        private int id;
        private int age;
        private int height;

        public StudentBuilder(String name,int id)
        {
            this.name = name;
            this.id = id;
        }

        public StudentBuilder age(int age)
        {
            this.age=age;
            return this;
        }

        public StudentBuilder height(int height)
        {
            this.height = height;
            return this;
        }

        public Student build()
        {
            return new Student(this);
        }
    }

    public String getName() {
        return name;
    }

    public int getId() {
        return id;
    }

    public int getAge() {
        return age;
    }

    public int getHeight() {
        return height;
    }

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

    public static void main(String[] args) {
        Student student = new StudentBuilder("codingway",111).age(18).height(2).build();
        System.out.println(student.toString());
    }
}

首先將Student類中的成員變量都聲明爲private final,並且將構造函數也聲明爲private,這樣子就保證了無法通過Student的構造函數在構建Student Bean,並且成員變量也無法被修改,對外只提供getter方法用於或者成員變量。

隨後構造一個StudentBuilder類,StudentBuilder類是一個靜態類 ,其構造函數可以設置爲Student必須的屬性,例如id和name,其他可選的變量放到方法中,例如age和height,每個方法返回StudentBuilder本身。

Student對象只有一個構造函數,其參數就是StudentBuilder,於是所有成員變量地設置由StudentBuilder接管了,而StudentBuilder控制了哪些成員變量必須賦值,哪些是可選的,最後通過build方法構造Student Bean。使用者一目瞭然,鏈式調用更加清晰。

這就是Builder模式被大家所喜愛,並且在很多著名的開源項目中被採用的原因,特別是在一些需要配置環境參數並且參數衆多的場景下。

更多內容

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