一.静态工厂和构造器的局限性
面对需要大量可选参数才能构建对象时,静态工厂和构造器并不能随着可选参数的增加而合理扩展。
假设创建一个类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模式,但各有优劣. 当 ...
随机推荐
-
linux之基础命令大全
作为测试人员对linux系统命令必须非常熟悉,尤其对于高级测试工程师,从事性能方面测试,就更需要对linux命令了如指掌,这里只对部分常用命令做解释,想深入学习请关注后续文章 ctrl z 终止当前 ...
-
大气散射 GPU Gems2 Chapter 16. Accurate Atmospheric Scattering
效果图 这次先上效果图*4 散射概念 光线击中空气中的微小颗粒后的偏折导致了光线的散射.我们看到的阳光应该是由视线上的散射在视线方向上的集合.如果由地面的反射,还要加上经过散射计算的地面反射. Ray ...
-
Lucene系列-近实时搜索(1)
近实时搜索(near-real-time)可以搜索IndexWriter还未commit的内容,介于immediate和eventual之间,在数据比较大.更新较频繁的情况下使用.本文主要来介绍下如何 ...
-
maven pox.xml 设置主入口配置节点
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven ...
-
Microsoft Mole原理及常见问题整理
Moles与Moq(Rhino.Mocks)比较 作用范围 Moq与Rhino.Mocks这类的Mock是对Interface或AbstractClass做Mock, 而Moles是Mock整个 ...
-
LSM Tree解析
引言 众所周知传统磁盘I/O是比较耗性能的,优化系统性能往往需要和磁盘I/O打交道,而磁盘I/O产生的时延主要由下面3个因素决定: 寻道时间(将磁盘臂移动到适当的柱面上所需要的时间,寻道时移动到相邻柱 ...
-
C++的显示转换
利用显示转换使得我们可以很容易发现它们,因为通过名字就能找到: static_cast 用于“良性”和“适度良性”转换,包括不用强制转换 const_cast 对“const”和“volatil ...
-
NSIS:简单按钮美化插件SkinButton,支持透明PNG图片。
原文 NSIS:简单按钮美化插件SkinButton,支持透明PNG图片. 征得作者贾可的同意,特发布按钮美化插件SkinButton. 插件说明: 使用GDI+库写的一个简单按钮美化插件,支持透明P ...
-
LeetCode OJ 41. First Missing Positive
Given an unsorted integer array, find the first missing positive integer. For example,Given [1,2,0] ...
-
CITS1401 Computational Thinking with Python
Department of Computer Science and Software EngineeringCITS1401 ComputationalThinking with PythonPro ...