构造器参数过多时考虑使用构建器(Builder)
一.静态工厂和构造器的局限性
面对需要大量可选参数才能构建对象时,静态工厂和构造器并不能随着可选参数的增加而合理扩展。
假设创建一个类Person需要使用大量的可选参数,其中两个参数是必填的,剩下的都是可选的,面对这种情况在使用静态工厂和构造器创建对象时通常使用叠加的方式实现:
/**
* @描述 为了方便演示,就设计四个可选参数
**/
public class Person { private final String name; // 必填
private final int age; // 必填 private final int gender; // 可选
private final String tel; // 可选
private final String address; //可选
private final String school; // 可选 // 使用叠加的方式处理可选参数
public Person(String name, int age) {
this(name, age, 0);
} public Person(String name, int age, int gender) {
this(name, age, gender, null);
} public Person(String name, int age, int gender, String tel) {
this(name, age, gender, tel, null);
} public Person(String name, int age, int gender, String tel, String address) {
this(name, age, gender, tel, address, null);
} public Person(String name, int age, int gender, String tel, String address, String school) {
this.name = name;
this.age = age;
this.gender = gender;
this.tel = tel;
this.address = address;
this.school = school;
}
}
上例中如果可选参数变得越来越多,那么代码维护上将会很困难,并且使用者也容易出错(比如不小心传错了位置顺序),所以这种重叠构造器模式在处理可选参数不多的情况是可行的,但是参数多了后就不合适了。
除了使用叠加的方式外,还可以使用setter处理这种情况(我们一般工作中应该大部分就是这样)即使用无参构造器创建对象,然后调用setter设置必要参数以及可选参数:
/**
* @描述 为了方便演示,就设计四个可选参数
**/
public class Person { private String name = ""; // 必填
private int age = 18; // 必填 private int gender = 0; // 可选
private String tel = ""; // 可选
private String address = ""; //可选
private String school = ""; // 可选 public void setName(String name) {
this.name = name;
} public void setAge(int age) {
this.age = age;
} public void setGender(int gender) {
this.gender = gender;
} public void setTel(String tel) {
this.tel = tel;
} public void setAddress(String address) {
this.address = address;
} public void setSchool(String school) {
this.school = school;
} public static void main(String[] args) {
Person p = new Person();
p.setName("TT");
p.setAge(18); p.setGender(1);
p.setTel("110");
p.setAddress("中国");
p.setSchool("NJ");
}
}
使用setter比起叠加构造器创建对象要容易,代码可读性更好,但是存在严重缺陷。一个是对象的创建被分解到多次setter中,没法保证最后得到的对象就是想要的,因为在setter过程中参数可能被改变。与此相关的就是此种方式下类无法成为不可变类(使用了setter后,类中的字段没法设置成final),会存在线程安全问题。
二.使用Builder兼具安全性以及可读性
使用Builder构建器具有像重叠构造器那样的安全性(不可变类)同时还像setter具有很好的可读性。
Builder构建器方式是指不直接创建想要的对象,而是调用者先用必填参数调用构造器(或者静态工厂方法)得到builder对象。然后调用者使用builder对象调用类似setter方法来设置相关的可选参数。最后调用者在调用builder中无参的build方法生成不可变对象。Builder是自身所能构建类的静态成员类(静态内部类):
/**
* @描述 为了方便演示,就设计四个可选参数
**/
public class Person { private final String name; // 必填
private final int age; // 必填 private final int gender; // 可选
private final String tel; // 可选
private final String address; //可选
private final String school; // 可选 public static class Builder {
private final String name; // 必填
private final int age; // 必填 private int gender = 0; // 可选
private String tel = ""; // 可选
private String address = ""; //可选
private String school = ""; // 可选 public Builder(String name, int age) {
this.name = name;
this.age = age;
} public Builder gender(int gender) {
this.gender = gender;
return this;
} public Builder tel(String tel) {
this.tel = tel;
return this;
} public Builder address(String address) {
this.address = address;
return this;
} public Builder school(String school) {
this.school = school;
return this;
} public Person build() {
return new Person(this);
}
} private Person(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.gender = builder.gender;
this.tel = builder.tel;
this.address = builder.address;
this.school = builder.school;
} public static void main(String[] args) { Person person = new Builder("WH", 18)
.gender(1)
.tel("110")
.address("WH")
.school("NJ")
.build(); }
}
上面使用Builder构造器时可以保证Person是不可变类,同时使用链式调用代码可读性更好易于阅读。
可以在build方法或者类似setter的方法中对参数进行校验,使代码更健壮。
Builder模式十分灵活,利用一个builder对象可以创建多个对象,且在创建对象时可以对参数进行修改,比如自动补全某参数或者对某参数做计算后在赋值或者自动生成主键id。
Builder构建器的对象还可以作为参数传递给创建对象的方法,让创建对象的方法利用Builder构建器生成对象,同时利用范型就可以在还没有Builder的情况下编写代码:
// 利用范型定义通用的Builder
public interface Builder<T> {
T build();
} // 利用builder对象创建对象的方法
public Person newInstance(Builder<? extends Person> builder) {
return builder.build();
}
三.利用反射创建对象存在的问题
一帮我们也会使用Class.newInstance()创建对象,首先通过Class.newInstance创建对象默认调用目标对象中的无参构造器(private修饰的无参也算,默认的无参也算),那么当存在多个构造器时newInstance就无效了,且在编译阶段是没法发现这个错误的仅运行时才知道:
public class Parent { // 使默认无参构造器失效
public Parent(int i) {} public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class clz = Parent.class;
Parent p = (Parent) clz.newInstance();
} }
上例在运行时会报错。
四.Builder构建器的缺点
为了创建目标对象需要先创建Builder对象,在十分注重性能的情况下可能对性能产生影响。同时在代码量上比重叠构造器还要冗长。所以尽量在参数较多时使用Builder构建器。如果一开始参数不多可是后面参数可能会增加变得比较多那就最好一开始使用Builder构建器,因为如果开始使用的重叠构造器或者静态工厂方法,等到多参数时改成Builder构建器,那么过时的构造器或静态工厂方法由于被使用不能删除会使代码结构显得不协调。简单的说,如果类的构造器或者静态工厂方法中具有多个参数,特别是大多数参数还是可选的时候,使用Builder构建器是个不错的选择。使用Builder构建器的调用者的代码更易编写和阅读,同时使用构建器使目标类不可变从而更线程安全。
构造器参数过多时考虑使用构建器(Builder)的更多相关文章
- 遇到多个构造器参数时要考虑用构建器 builder 模式 JavaBean 线程安全
effective java p9 JavaBeans模式阻止了把类做成不可变的可能,这需要程序员付出额外的努力来确保它的线程安全.
- 【读书笔记 - Effective Java】02. 遇到多个构造器参数时要考虑用构建器
类有多个可选参数的解决方案: 1. 重叠构造器模式可行,但是当有许多参数的时候,客户端代码会很难编写,并且仍然较难以阅读. 2. JavaBeans模式,调用一个无参构造器来创造对象,然后调用sett ...
- 链式编程:遇到多个构造器参数(Constructor Parameters)时要考虑用构建器(Builder)
public class NutritionFacts { private final int servingSize; private final int servings; private fin ...
- effective java之使用构建器来创建对象
第二章第2条:遇到多个构造器参数时要考虑使用构建器(builder) 就是建造者模式(不直接生成想要的对象,而是让客户端利用所有有必要的参数调用构造器或者静态工厂)直接上代码 package com. ...
- Java构建器(多个构造器参数)
今天看netty权威指南,第一次听说构建器,百度了几个博客,但是并没有通俗易懂一点儿的,综合别人的博客,总结如下: 1. 构建器是什么? 当创建对象需要传入多个参数的时候我们通常会根据参数的数量写不同 ...
- Item 2---遇到构造器具有多个参数时,要考虑用构建器;Builder模式
问题,面对这种一个构造器具备多个参数的问题,现有的做法是使用重叠构造器的方式,该方式存在的问题: public class NutritionFacts { private final int ser ...
- Java构造器与构建器的使用
我们在平常类的构建过程中,可能会面临很多问题,可扩张性.安全性等等.想象一下,这样一个场景,我们现在要创建一个类,其中有6个属性,其中又有4个属性的值是不太确定的(可能某个对象就不需要其中的某个值), ...
- java构造器和构建器
本文摘自:https://blog.csdn.net/wh2827991/article/details/79013115 在实例化一个类的过程中,通常会遇到多个参数的构造函数,但如果有些参数是非必需 ...
- Java 构造器 遇到多个构造器时要考虑用构建器
静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数. 当一个类中有若干个必选属性和多个可选属性时,采用重叠构造器模式.JavaBeans模式或者Builder模式,但各有优劣. 当 ...
随机推荐
- 洛谷P3165 [CQOI2014]排序机械臂 Splay维护区间最小值
可以将高度定义为小数,这样就完美的解决了优先级的问题. Code: #include<cstdio> #include<algorithm> #include<cstri ...
- K3 新单到老单关联字段的添加
新单到老单字段的添加分为两种: 一种为文本字段信息的关联,新单与老单字段的信息均为文本字段: 另一种为基础资料信息的关联,新单与老单均为基础资料字段信息. K3 WISE 11.0中存储老 ...
- node——express实现hello world
创建文件夹,在文件夹内再创建index.js 1.package.json npm init -y 2.安装 npm install express ---save 3.index.js //入口文件 ...
- 用C#调用Windows API向指定窗口发送按键消息
一.调用Windows API. C#下调用Windows API方法如下: 1.引入命名空间:using System.Runtime.InteropServices; 2.引用需要使用的方法,格式 ...
- 搞定PHP面试 - 变量知识点整理
一.变量的定义 1. 变量的命名规则 变量名可以包含字母.数字.下划线,不能以数字开头. $Var_1 = 'foo'; // 合法 $var1 = 'foo'; // 合法 $_var1 = 'fo ...
- PHP算法之判断是否是质数
质数的定义 质数又称素数.一个大于1的自然数,除了1和它自身外,不能整除其他自然数的数叫做质数:否则称为合数. 实现思路 循环所有可能的备选数字,然后和中间数以下且大于等于2的整数进行整除比较,如果能 ...
- 《黑白团团队》第八次团队作业:Alpha冲刺 第五天
项目 内容 作业课程地址 任课教师首页链接 作业要求 团队项目 填写团队名称 黑白团团队 填写具体目标 认真负责,完成项目 团队项目Github仓库地址链接. 第五天 日期:2019/6/19 成员 ...
- @Autowired 作用范围
一.@AutoWired 可以作用于:构造器.方法.参数.属性 二.作用在方法上 @Component public class Student{ private Book book; public ...
- selenium+java实现浏览器前进、后退和刷新
- (转)关于使用iText导出pdf
一.iText简介 iText是著名的开放源码的站点sourceforge一个项目,是用于生成PDF文档的一个java类库.通过iText不仅可以生成PDF或rtf的文档,而且可以将XML.Html文 ...