创建型设计模式-建造者(Builder)模式
Posted 风流 少年
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了创建型设计模式-建造者(Builder)模式相关的知识,希望对你有一定的参考价值。
在了解建造者模式之前需要先了解一下重叠构造器模式和JavaBeans模式,构建者模式是为了优化前两种模式的弊端而产生的。
一:重叠构造器(telescoping constructor)模式
假如一个类中有很多字段,其中只有少数几个字段是必选的(required),其余大部分字段都是可选的(optional),那么该如何创建对象呢?
我们创建一个"营养成分表"类,营养成分在每个食物的包装盒上都有明确标明各成分的含量。我们假如能量、蛋白质这两个字段是必选的,由于每种食物可能包含的成分不同我们假如脂肪、钠、维他命、钙、铁等字段是可选的。我们认为这些成分的值一旦设置值就不能修改,所以我们将每个字段都使用final修饰符来修饰。
如何实例化对象?我们可以通过构造方法来实例化, 我们需要为所有字段生成一个最完整的构造器,只通过这一个构造器来实例化。
/**
* 营养成分表
*
* @author Mengday Zhang
* @version 1.0
* @since 2019-08-08
*/
public class NutritionFacts
/** 能量 required */
private final int energy;
/** 蛋白质 required */
private final double protein;
/** 脂肪 optional */
private final double fat;
/** 钠 optional */
private final int sodium;
/** 维他命 optional */
private final int vitamin;
/** 钙 optional */
private final int calcium;
/** 铁 optional */
private final int iron;
public NutritionFacts(int energy, double protein, double fat, int sodium, int vitamin, int calcium, int iron)
this.energy = energy;
this.protein = protein;
this.fat = fat;
this.sodium = sodium;
this.vitamin = vitamin;
this.calcium = calcium;
this.iron = iron;
public static void main(String[] args)
// 只初始化必要参数,其它可选参数也不得不赋一个默认值,为可选参数也要必须设置默认值显得非常不优雅
NutritionFacts nutritionFacts = new NutritionFacts(1216, 9.2, 0.0, 0, 0, 0, 0);
// 同时初始化所有必要参数和部分可选参数,其它可选参数也不得不设置一个默认值
NutritionFacts nutritionFacts2 = new NutritionFacts(1216, 9.2, 13.9, 0, 0, 0, 0);
// 同时初始化所有必要参数和部分可选参数,其它可选参数也不得不设置一个默认值
NutritionFacts nutritionFacts3 = new NutritionFacts(1216, 9.2, 0.0, 218, 0, 0, 0);
重叠构造器模式的缺点
只有一个有全部字段的构造器,在使用起来非常笨拙,当我们实例化对象时想只给部分字段赋值,此时也不得不把其它不想初始化的字段也要设置默认值,这种写法非常的不优雅,而且如果类中的字段很多的话,一个构造器包含所有字段,可能在设置值的时候容易搞错顺序从而设置值错误。
为了缓解这种情况我们需要构造多种构造器,如必须的字段设置一个构造器,然后再必须字段上一个可选字段的构造器,依次类推,直到一个拥有完全字段的构造器,这样会比只有一个构造器稍微好一点。
/**
* 营养成分表
*
* @author Mengday Zhang
* @version 1.0
* @since 2019-08-08
*/
public class NutritionFacts
/** 能量 required */
private final int energy;
/** 蛋白质 required */
private final double protein;
/** 脂肪 optional */
private final double fat;
/** 钠 optional */
private final int sodium;
/** 维他命 optional */
private final int vitamin;
/** 钙 optional */
private final int calcium;
/** 铁 optional */
private final int iron;
public NutritionFacts(int energy, double protein)
this(energy, protein, 0.0, 0, 0, 0, 0);
public NutritionFacts(int energy, double protein, double fat)
this(energy, protein, fat, 0, 0, 0, 0);
public NutritionFacts(int energy, double protein, double fat, int sodium)
this(energy, protein, fat, sodium, 0, 0, 0);
public NutritionFacts(int energy, double protein, double fat, int sodium, int vitamin)
this(energy, protein, fat, sodium, vitamin, sodium, 0);
public NutritionFacts(int energy, double protein, double fat, int sodium, int vitamin, int calcium)
this(energy, protein, fat, sodium, vitamin, calcium, 0);
public NutritionFacts(int energy, double protein, double fat, int sodium, int vitamin, int calcium, int iron)
this.energy = energy;
this.protein = protein;
this.fat = fat;
this.sodium = sodium;
this.vitamin = vitamin;
this.calcium = calcium;
this.iron = iron;
public static void main(String[] args)
// 只初始化必要参数
NutritionFacts nutritionFacts = new NutritionFacts(1216, 9.2);
// 同时初始化所有必要参数和部分可选参数
NutritionFacts nutritionFacts2 = new NutritionFacts(1216, 9.2, 13.9);
// 同时初始化所有必要参数和部分可选参数
NutritionFacts nutritionFacts3 = new NutritionFacts(1216, 9.2, 0.0, 218, 0, 0, 0);
// 同时初始化所有必要参数和部分可选参数
NutritionFacts nutritionFacts4 = new NutritionFacts(1216, 9.2, 0.0, 218, 0, 56, 0);
我们虽然增加了很多个构造器,确实在实例化时只设置了自己关心的字段,看起来代码也清新不少。但是这需要写很多很多个构造器,这显然不太实际。为了解决这种问题我们继续使用JavaBeans模式来优化。
二:JavaBeans模式
JavaBeans模式就是我们一直在使用的模式,就是给一个无参构造函数,然后通过set方法为关心的字段赋值。此时可以想对哪个字段赋值就可以对哪个字段赋值。
/**
* 营养成分表
*
* @author Mengday Zhang
* @version 1.0
* @since 2019-08-08
*/
public class NutritionFacts
/** 能量 required */
private int energy;
/** 蛋白质 required */
private double protein;
/** 脂肪 optional */
private double fat;
/** 钠 optional */
private int sodium;
/** 维他命 optional */
private int vitamin;
/** 钙 optional */
private int calcium;
/** 铁 optional */
private int iron;
public NutritionFacts()
public int getEnergy()
return energy;
public void setEnergy(int energy)
this.energy = energy;
public double getProtein()
return protein;
public void setProtein(double protein)
this.protein = protein;
public double getFat()
return fat;
public void setFat(double fat)
this.fat = fat;
public int getSodium()
return sodium;
public void setSodium(int sodium)
this.sodium = sodium;
public int getVitamin()
return vitamin;
public void setVitamin(int vitamin)
this.vitamin = vitamin;
public int getCalcium()
return calcium;
public void setCalcium(int calcium)
this.calcium = calcium;
public int getIron()
return iron;
public void setIron(int iron)
this.iron = iron;
public static void main(String[] args)
// 调用无参构造器进行实例化,调用set方法,想对哪个字段赋值就对哪个字段赋值
NutritionFacts nutritionFacts = new NutritionFacts();
nutritionFacts.setEnergy(1216);
nutritionFacts.setProtein(9.2);
nutritionFacts.setSodium(218);
nutritionFacts.setCalcium(56);
JavaBeans模式这种方式可以为任意字段赋值,代码看起来也很清新,但是这种方式不能对字段使用final修饰符来修饰, 这不满足我们的需求。
三:建造者(Builder)模式
为了既能对字段使用final修饰符修饰,也达到可以优雅的对关系的字段赋值,我们继续使用建造者(Builder)模式优化。
设计模式的代码模式都是固定的(有些设计模式存在一些变体),建造者设计模式的特点有:
- 所有字段被final修饰
- 如果其它类需要访问属性值,可以为字段提供getter方法,设计模式中并没有提到这一点
- 提供一个私有的构造器,构造器参数为内部静态类Builder
- 静态内部类的类名一般起名为Builder,该类中也有一份外部类的所有属性
- 静态内部类Builder中有每个属性的set方法,set方法的名字可以是普通的set命名方式即set作为前缀(如setName), 也可以不使用set前缀,set方法的名字和属性的名字完全一样(如name),两种做法都可以,而且每个set方法的返回类型都为当前类Builder,这样做是可以通过链式语法调用setter方法
- 静态内部类Builder中提供一个build()方法用于返回外部类实例
/**
* 营养成分表
*
* @author Mengday Zhang
* @version 1.0
* @since 2019-08-08
*/
public class NutritionFacts
/** 能量 required */
private final int energy;
/** 蛋白质 required */
private final double protein;
/** 脂肪 optional */
private final double fat;
/** 钠 optional */
private final int sodium;
/** 维他命 optional */
private final int vitamin;
/** 钙 optional */
private final int calcium;
/** 铁 optional */
private final int iron;
public int getEnergy()
return energy;
public double getProtein()
return protein;
public double getFat()
return fat;
public int getSodium()
return sodium;
public int getVitamin()
return vitamin;
public int getCalcium()
return calcium;
public int getIron()
return iron;
private NutritionFacts(Builder builder)
this.energy = builder.energy;
this.protein = builder.protein;
this.fat = builder.fat;
this.sodium = builder.sodium;
this.vitamin = builder.vitamin;
this.calcium = builder.calcium;
this.iron = builder.iron;
public static class Builder
private int energy;
private double protein;
private double fat;
private int sodium;
private int vitamin;
private int calcium;
private int iron;
public Builder(int energy, double protein)
this.energy = energy;
this.protein = protein;
public Builder energy(int energy)
this.energy = energy;
return this;
public Builder protein(double energy)
this.protein = protein;
return this;
public Builder fat(double fat)
this.fat = fat;
return this;
public Builder sodium(int sodium)
this.sodium = sodium;
return this;
public Builder vitamin(int vitamin)
this.vitamin = vitamin;
return this;
public Builder calcium(int calcium)
this.calcium = calcium;
return this;
public Builder iron(int iron)
this.iron = iron;
return this;
public NutritionFacts build()
// 检查必要字段是否都有值,如果没有则抛出异常
return new NutritionFacts(this);
public class Main
public static void main(String[] args)
NutritionFacts nutritionFacts = new NutritionFacts.Builder(1216, 9.2)
.sodium(218)
.calcium(56)
.build();
建造者模式的思想:建造者模式不直接生成想要的对象,而是先创建一个Builder对象,然后再调用Builder对象的build方法来生成想要的对象。
建造者(Builder)模式适用场景
- Java Builder模式主要是用一个内部类去实例化一个对象,避免一个类出现过多构造函数,而且构造函数如果出现默认参数的话,很容易出错。
- 通常使用建造者模式创建的对象的属性都是不可变的,不能再次对字段进行修改
- 一般用于配置参数
优点
建造者模式代码比较优雅,比较容易阅读,可以通过链式语法一直调用下去。
缺点
构造器需要写更多的代码,不过现在可以使用Lombok插件,只需要在类上使用一个注解(@Builder)就搞定了
四:Lombok
@Builder
public class NutritionFacts
/** 能量 required */
private final int energy;
/** 蛋白质 required */
private final double protein;
/** 脂肪 optional */
private final double fat;
/** 钠 optional */
private final int sodium;
/** 维他命 optional */
private final int vitamin;
/** 钙 optional */
private final int calcium;
/** 铁 optional */
private final int iron;
@Builder编译后的.class文件
public class NutritionFacts
private final int energy;
private final double protein;
private final double fat;
private final int sodium;
private final int vitamin;
private final int calcium;
private final int iron;
NutritionFacts(final int energy, final double protein, final double fat, final int sodium, final int vitamin, final int calcium, final int iron)
this.energy = energy;
this.protein = protein;
this.fat = fat;
this.sodium = sodium;
this.vitamin = vitamin;
this.calcium = calcium;
this.iron = iron;
public static NutritionFacts.NutritionFactsBuilder builder()
return new NutritionFacts.NutritionFactsBuilder();
public static class NutritionFactsBuilder
private int energy;
private double protein;
private double fat;
private int sodium;
private int vitamin;
private int calcium;
private int iron;
NutritionFactsBuilder()
public NutritionFacts.NutritionFactsBuilder energy(final int energy)
this.energy = energy;
return this;
public NutritionFacts.NutritionFactsBuilder protein(final double protein)
this.protein = protein;
return this;
public NutritionFacts.NutritionFactsBuilder fat(final double fat)
this.fat = fat;
return this;
public NutritionFacts.NutritionFactsBuilder sodium(final int sodium)
this.sodium = sodium;
return this;
public NutritionFacts.NutritionFactsBuilder vitamin(final int vitamin)
this.vitamin = vitamin;
return this;
public NutritionFacts.NutritionFactsBuilder calcium以上是关于创建型设计模式-建造者(Builder)模式的主要内容,如果未能解决你的问题,请参考以下文章