【Java】多参构造器与Builder模式及链式调用的实际运用

多参构造器与Builder模式及链式调用的实际运用

小羊子说发布于 2 月 6 日

1. 前言:

首先,回顾标准的用法

在《Effective Java 第2版》中有提到,遇到多个构造器参数时要考虑使用构建器(Builder模式)。相比于重叠构造器(telescoping constructor)模式JavaBeans模式Builder模式实现的对象更利于使用。

/**

* <pre>

* author : June Yang

* time : 2021/02/06

* desc :

* version: 1.0

* </pre>

*/

public class NutritionFacts {

private final int servingSize;

private final int servings;

private final int calories;

private final int fat;

private final int sodium;

private final int carbohydrate;

public static class Builder {

//必须注入的参数,可不用初始化

private final int servingSize;

private final int servings;

//可选择注入的参数

private int calories = 0;

private int fat = 0;

private int sodium = 0;

private int carbohydrate = 0;

public Builder(int servingSize, int servings) {

this.servingSize = servingSize;

this.servings = servings;

}

public Builder setCalories(int calories) {

this.calories = calories;

return this;

}

public Builder calories(int val) {

this.calories = val;

return this;

}

public Builder carbohydrate(int val) {

this.carbohydrate = val;

return this;

}

public Builder sodium(int val) {

this.sodium = val;

return this;

}

public NutritionFacts build() {

return new NutritionFacts(this);

}

}

@Override

public String toString() {

return "NutritionFacts{" +

"servingSize=" + servingSize +

", servings=" + servings +

", calories=" + calories +

", fat=" + fat +

", sodium=" + sodium +

", carbohydrate=" + carbohydrate +

'}';

}

//私有构造方法

private NutritionFacts(Builder builder) {

servingSize = builder.servingSize;

servings = builder.servings;

calories = builder.calories;

fat = builder.fat;

sodium = builder.sodium;

carbohydrate = builder.carbohydrate;

}

public static void main(String[] args) {

NutritionFacts bean = new NutritionFacts.

Builder(8, 20)

.calories(30)

.build();

}

}

调用时:

   public static void main(String[] args) {

NutritionFacts bean = new NutritionFacts.

Builder(8, 20)

.calories(30)

.build();

}

2. Android项目的中运用

Android开发中经常遇到这样的代码:

Retrofit retrofit = new Retrofit.Builder()

.baseUrl("https://api.xxx.com/")

.build();

或者

new AlertDialog.Builder(this)

.setTitle("TitleName")

.setMessage("Message")

.setCancelable(true)

.setOnCancelListener(new DialogInterface.OnCancelListener() {

@Override

public void onCancel(DialogInterface dialog) {

//...

}

})

.show();

builder实现的链式调用看上去如此优雅。于是项目开发中我们也想实际用起来。

3. 常见的两种构建方式

在日常开发中,我们经常需要给某个对象的变量赋值,这个赋值的过程称为 对象的构建。

比如现在有个 Person 类,它有几个成员变量:

//固定不变的对象,一般变量需要声明为 final

private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在

private String mLocation; //可选

private String mJob; //可选

3.1 常见的构建方式之一:定义多个重载的构造函数

public class PersonOne {

//固定不变的对象,一般变量需要声明为 final

private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在

private String mLocation; //可选

private String mJob; //可选

public PersonOne(String name) {

mName = name;

}

public PersonOne(String location, String name) {

mLocation = location;

mName = name;

}

public PersonOne(String name, String location, String job) {

mName = name;

mLocation = location;

mJob = job;

}

}

这种方式的优点:简单。

所以缺点是
只适用于成员变量少的情况,太多了不容易理解、维护。不优雅。

3.2 常见的构建方式之二:使用 setter 方法挨个构造

public class PersonTwo {

//固定不变的对象,一般变量需要声明为 final

private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在

private String mLocation; //可选

private String mJob; //可选

public PersonTwo(String s) {

this.mName = s;

}

public String getName() {

return mName;

}

public String getLocation() {

return mLocation;

}

public void setLocation(String location) {

mLocation = location;

}

public String getJob() {

return mJob;

}

public void setJob(String job) {

mJob = job;

}

}

这种方式也是常见的构造方式,它的好处是:易于阅读,并且可以只对有用的成员变量赋值;

缺点是:

  • 成员变量不可以是 final 类型,失去了不可变对象的很多好处;
  • 对象状态不连续,你必须调用 4 次 setter 方法才能得到一个具备 4 个属性值得变量,在这期间用户可能拿到不完整状态的对象。

而且使用起来也不好看:

   PersonTwo personTwo = new PersonTwo("shixin");

personTwo.setJob("Android");

personTwo.setLocation("成都");

如果有 N 个属性岂不是要 personTwo.setXXX N 回?不优雅!

即使把 setXXX 方法返回值改成当前构造类,但还是不满足最重要的缺点的第二点:

什么意思呢?

这种方式是 先创建对象、后赋值,用户不知道什么时候拿到的对象是完整的,构建完成的。很有可能你只 set 了一两个属性就返回了,一些必要的属性没有被赋值。

3.3 优雅的构建方式:变种 Builder 模式(回到标准用法上)

为了解决上述两种构建方式,伟大的程序员们创造出了 变种 Builder 模式

先来看看用 变种 Builder 模式怎么实现上述 Person 对象的构建吧:

public class PersonThree {

//固定不变的对象,一般变量需要声明为 final

private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在

private String mLocation; //可选

private String mJob; //可选

/**

* 构造方法的参数是它的 静态内部类,使用静态内部类的变量一一赋值

* @param builder

*/

public PersonThree(Builder builder) {

this.mName = builder.mName;

this.mLocation = builder.mLocation;

this.mJob = builder.mJob;

}

/**

* PersonTree 的静态内部类,成员变量和 PersonTree 的一致

*/

public static class Builder{

private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在

private String mLocation; //可选

private String mJob; //可选

/**

* 含必选参数的构造方法

* @param name

*/

public Builder(String name) {

mName = name;

}

public Builder setLocation(String location) {

mLocation = location;

return this;

}

public Builder setJob(String job) {

mJob = job;

return this;

}

/**

* 最终构建方法,返回一个 PersonTree 对象,参数是当前 Builder 对象

* @return

*/

public PersonThree build(){

return new PersonThree(this);

}

}

}

可以看到,变种 Builder 模式包括以下内容:

  • 在要构建的类内部创建一个静态内部类 Builder
  • 静态内部类的参数与构建类一致
  • 构建类的构造参数是 静态内部类,使用静态内部类的变量一一赋值给构建类
  • 静态内部类提供参数的 setter 方法,并且返回值是当前 Builder 对象
  • 最终提供一个 build 方法构建一个构建类的对象,参数是当前 Builder 对象

  new PersonThree.Builder("xiaoyangzishuo")

.setLocation("ChengDu ")

.setJob("Android Develop")

.build();

变种 Builder 模式目的在于减少对象创建过程中引入的多个构造函数、可选参数以及多个 setter 过度使用导致的不必要的复杂性。

好处就是文章开头所说的:

  • 看起来很整齐;
  • 先赋值,后创建对象。

最终调用 build() 方法才创建了构建类的对象,保证了状态的完整性。

缺点嘛,就是需要额外写的代码多了点。

3.4 Android Studio 中使用插件自动生成 变种 Builder 模式代码

下载插件 Inner Builder 也可以快速实现Builder的构建,代码同上。略。

4. 链式调用(其他builder的一种变种方式)

class Calculator {

private int total;

public Calculator(int cardinality) {

this.total = cardinality;

}

public Calculator add(int addend) {

this.total += addend;

return this;

}

public Calculator minus(int minus) {

this.total -= minus;

return this;

}

public Calculator multiply(int multiplier) {

this.total *= multiplier;

return this;

}

public Calculator divideBy(int divisor) {

this.total /= divisor;

return this;

}

public Calculator calculate() {

return this;

}

public int getTotal() {

return total;

}

}

将方法的返回值类型指定为当前 Class ,那么,在方法体的最后可以直接 return this ,从而可以形成一个类似于 Builder Patern 的效果。

用法如下:

Calculator calculator = new Calculator(5)

.add(3)

.minus(4)

.multiply(2)

.divideBy(4)

.calculate(); //最后一行不要也能创建成功,好多文章都是说要添加,不要似乎也没有什么影响 不知你怎么看,求解惑。

总结

经典的 Builder 模式定义为:

它的重点在于:抽象出对象创建的具体步骤到一个接口,通过调用不同的接口实现,从而得到不同的结果。

Builder 模式在 Android 开发中演变出了 变种 Builder 模式,它除了具备经典构建者模式的功能,还简化了构建的过程,使得创建过程更加简单、直观。

参考:

1.《Effective Java》

2.《Effective Java》读书笔记02-多参构造器与Builder模式

2.变种 Builder 模式:优雅的对象构建方式

3. 使用Builder模式创建复杂可选参数对象

4《effective java》builder模式一些思考

5. EffectiveJava---Builder设计模式

从 Java Builder Pattern 到 return this 链式调用

Java高效编程之Builder模式

javaandroid对象设计思想设计模式新说

阅读 24更新于 2 月 6 日

本作品系原创,采用《署名-非商业性使用-禁止演绎 4.0 国际》许可协议


Android实用技能指南

Android开发一枚,专注分享工作中用到的技术总结,欢迎交流。CSDN 博客:[链接]

avatar

小羊子说

Android开发一枚,专注分享工作中用到的技术总结,欢迎交流。

5 声望

2 粉丝

0 条评论

得票时间

avatar

小羊子说

Android开发一枚,专注分享工作中用到的技术总结,欢迎交流。

5 声望

2 粉丝

宣传栏

1. 前言:

首先,回顾标准的用法

在《Effective Java 第2版》中有提到,遇到多个构造器参数时要考虑使用构建器(Builder模式)。相比于重叠构造器(telescoping constructor)模式JavaBeans模式Builder模式实现的对象更利于使用。

/**

* <pre>

* author : June Yang

* time : 2021/02/06

* desc :

* version: 1.0

* </pre>

*/

public class NutritionFacts {

private final int servingSize;

private final int servings;

private final int calories;

private final int fat;

private final int sodium;

private final int carbohydrate;

public static class Builder {

//必须注入的参数,可不用初始化

private final int servingSize;

private final int servings;

//可选择注入的参数

private int calories = 0;

private int fat = 0;

private int sodium = 0;

private int carbohydrate = 0;

public Builder(int servingSize, int servings) {

this.servingSize = servingSize;

this.servings = servings;

}

public Builder setCalories(int calories) {

this.calories = calories;

return this;

}

public Builder calories(int val) {

this.calories = val;

return this;

}

public Builder carbohydrate(int val) {

this.carbohydrate = val;

return this;

}

public Builder sodium(int val) {

this.sodium = val;

return this;

}

public NutritionFacts build() {

return new NutritionFacts(this);

}

}

@Override

public String toString() {

return "NutritionFacts{" +

"servingSize=" + servingSize +

", servings=" + servings +

", calories=" + calories +

", fat=" + fat +

", sodium=" + sodium +

", carbohydrate=" + carbohydrate +

'}';

}

//私有构造方法

private NutritionFacts(Builder builder) {

servingSize = builder.servingSize;

servings = builder.servings;

calories = builder.calories;

fat = builder.fat;

sodium = builder.sodium;

carbohydrate = builder.carbohydrate;

}

public static void main(String[] args) {

NutritionFacts bean = new NutritionFacts.

Builder(8, 20)

.calories(30)

.build();

}

}

调用时:

   public static void main(String[] args) {

NutritionFacts bean = new NutritionFacts.

Builder(8, 20)

.calories(30)

.build();

}

2. Android项目的中运用

Android开发中经常遇到这样的代码:

Retrofit retrofit = new Retrofit.Builder()

.baseUrl("https://api.xxx.com/")

.build();

或者

new AlertDialog.Builder(this)

.setTitle("TitleName")

.setMessage("Message")

.setCancelable(true)

.setOnCancelListener(new DialogInterface.OnCancelListener() {

@Override

public void onCancel(DialogInterface dialog) {

//...

}

})

.show();

builder实现的链式调用看上去如此优雅。于是项目开发中我们也想实际用起来。

3. 常见的两种构建方式

在日常开发中,我们经常需要给某个对象的变量赋值,这个赋值的过程称为 对象的构建。

比如现在有个 Person 类,它有几个成员变量:

//固定不变的对象,一般变量需要声明为 final

private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在

private String mLocation; //可选

private String mJob; //可选

3.1 常见的构建方式之一:定义多个重载的构造函数

public class PersonOne {

//固定不变的对象,一般变量需要声明为 final

private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在

private String mLocation; //可选

private String mJob; //可选

public PersonOne(String name) {

mName = name;

}

public PersonOne(String location, String name) {

mLocation = location;

mName = name;

}

public PersonOne(String name, String location, String job) {

mName = name;

mLocation = location;

mJob = job;

}

}

这种方式的优点:简单。

所以缺点是
只适用于成员变量少的情况,太多了不容易理解、维护。不优雅。

3.2 常见的构建方式之二:使用 setter 方法挨个构造

public class PersonTwo {

//固定不变的对象,一般变量需要声明为 final

private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在

private String mLocation; //可选

private String mJob; //可选

public PersonTwo(String s) {

this.mName = s;

}

public String getName() {

return mName;

}

public String getLocation() {

return mLocation;

}

public void setLocation(String location) {

mLocation = location;

}

public String getJob() {

return mJob;

}

public void setJob(String job) {

mJob = job;

}

}

这种方式也是常见的构造方式,它的好处是:易于阅读,并且可以只对有用的成员变量赋值;

缺点是:

  • 成员变量不可以是 final 类型,失去了不可变对象的很多好处;
  • 对象状态不连续,你必须调用 4 次 setter 方法才能得到一个具备 4 个属性值得变量,在这期间用户可能拿到不完整状态的对象。

而且使用起来也不好看:

   PersonTwo personTwo = new PersonTwo("shixin");

personTwo.setJob("Android");

personTwo.setLocation("成都");

如果有 N 个属性岂不是要 personTwo.setXXX N 回?不优雅!

即使把 setXXX 方法返回值改成当前构造类,但还是不满足最重要的缺点的第二点:

什么意思呢?

这种方式是 先创建对象、后赋值,用户不知道什么时候拿到的对象是完整的,构建完成的。很有可能你只 set 了一两个属性就返回了,一些必要的属性没有被赋值。

3.3 优雅的构建方式:变种 Builder 模式(回到标准用法上)

为了解决上述两种构建方式,伟大的程序员们创造出了 变种 Builder 模式

先来看看用 变种 Builder 模式怎么实现上述 Person 对象的构建吧:

public class PersonThree {

//固定不变的对象,一般变量需要声明为 final

private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在

private String mLocation; //可选

private String mJob; //可选

/**

* 构造方法的参数是它的 静态内部类,使用静态内部类的变量一一赋值

* @param builder

*/

public PersonThree(Builder builder) {

this.mName = builder.mName;

this.mLocation = builder.mLocation;

this.mJob = builder.mJob;

}

/**

* PersonTree 的静态内部类,成员变量和 PersonTree 的一致

*/

public static class Builder{

private final String mName; //必选,final 类型需要在 构造器中初始化,不允许不初始化它的构造器存在

private String mLocation; //可选

private String mJob; //可选

/**

* 含必选参数的构造方法

* @param name

*/

public Builder(String name) {

mName = name;

}

public Builder setLocation(String location) {

mLocation = location;

return this;

}

public Builder setJob(String job) {

mJob = job;

return this;

}

/**

* 最终构建方法,返回一个 PersonTree 对象,参数是当前 Builder 对象

* @return

*/

public PersonThree build(){

return new PersonThree(this);

}

}

}

可以看到,变种 Builder 模式包括以下内容:

  • 在要构建的类内部创建一个静态内部类 Builder
  • 静态内部类的参数与构建类一致
  • 构建类的构造参数是 静态内部类,使用静态内部类的变量一一赋值给构建类
  • 静态内部类提供参数的 setter 方法,并且返回值是当前 Builder 对象
  • 最终提供一个 build 方法构建一个构建类的对象,参数是当前 Builder 对象

  new PersonThree.Builder("xiaoyangzishuo")

.setLocation("ChengDu ")

.setJob("Android Develop")

.build();

变种 Builder 模式目的在于减少对象创建过程中引入的多个构造函数、可选参数以及多个 setter 过度使用导致的不必要的复杂性。

好处就是文章开头所说的:

  • 看起来很整齐;
  • 先赋值,后创建对象。

最终调用 build() 方法才创建了构建类的对象,保证了状态的完整性。

缺点嘛,就是需要额外写的代码多了点。

3.4 Android Studio 中使用插件自动生成 变种 Builder 模式代码

下载插件 Inner Builder 也可以快速实现Builder的构建,代码同上。略。

4. 链式调用(其他builder的一种变种方式)

class Calculator {

private int total;

public Calculator(int cardinality) {

this.total = cardinality;

}

public Calculator add(int addend) {

this.total += addend;

return this;

}

public Calculator minus(int minus) {

this.total -= minus;

return this;

}

public Calculator multiply(int multiplier) {

this.total *= multiplier;

return this;

}

public Calculator divideBy(int divisor) {

this.total /= divisor;

return this;

}

public Calculator calculate() {

return this;

}

public int getTotal() {

return total;

}

}

将方法的返回值类型指定为当前 Class ,那么,在方法体的最后可以直接 return this ,从而可以形成一个类似于 Builder Patern 的效果。

用法如下:

Calculator calculator = new Calculator(5)

.add(3)

.minus(4)

.multiply(2)

.divideBy(4)

.calculate(); //最后一行不要也能创建成功,好多文章都是说要添加,不要似乎也没有什么影响 不知你怎么看,求解惑。

总结

经典的 Builder 模式定义为:

它的重点在于:抽象出对象创建的具体步骤到一个接口,通过调用不同的接口实现,从而得到不同的结果。

Builder 模式在 Android 开发中演变出了 变种 Builder 模式,它除了具备经典构建者模式的功能,还简化了构建的过程,使得创建过程更加简单、直观。

参考:

1.《Effective Java》

2.《Effective Java》读书笔记02-多参构造器与Builder模式

2.变种 Builder 模式:优雅的对象构建方式

3. 使用Builder模式创建复杂可选参数对象

4《effective java》builder模式一些思考

5. EffectiveJava---Builder设计模式

从 Java Builder Pattern 到 return this 链式调用

Java高效编程之Builder模式

以上是 【Java】多参构造器与Builder模式及链式调用的实际运用 的全部内容, 来源链接: www.h5w3.com/113722.html

回到顶部