文章有参看其他博主博客:
- 考虑使用静态工厂方法替代构造方法[https://www.cnblogs.com/chenpi/p/5981084.html]
最近在阅读《Effectivce-java》的时候,里面提到了用静态工厂方法来给类创建对象,替代原有的构造方法。这里是说,并不是要取缔类中原有的构造器,而是说创建对象时不通过构造器,而是通过静态工厂方法来创建。这样做有它的有点,当然也有缺点,下面一起探究一下。
一、概述
在Java开发过程中,创建对象通常的做法是通过有参或者无参构造器创建对象。然而,你肯定遇到过这种情况:通常是一个某某实体类,有get,set方法,有多个带参构造器,只需保证参数顺序或者个数不同,很容易写出多个不同的带参构造器。然而,这样的做法导致的结果是该类的可读性过差,构造器冗余,如果没有方法注释说明,在调用构造器时还有可能调用错误的构造器,带来隐患BUG。
此时,静态工厂方法就显得很与众不同了。注意,静态工厂方法与【设计模式】中的工厂模式不同,并不是对应关系。此处的静态工厂方法仅仅也是一个普通的静态方法,只不过是我们赋予了它创造对象的意义。通过概念我们可以区分:
-
静态工厂方法通常指的是某个类里的静态方法,通过调用该静态方法可以得到属于该类的一个实例
-
工厂方法模式是一种设计模式,指的是让具体的工厂对象负责生产具体的产品对象,这里涉及多种工厂(类),多种对象(类),如内存工厂生产内存对象,CPU工厂生产CPU对象
-
静态工厂方法跟简单工厂模式倒有那么点像,不过区别也挺大,简单工厂模式里的静态工厂方法会创建各种不同的对象(不同类的实例),而静态工厂方法一般只创建属于该类的一个实例(包括子类)
二、使用静态工厂方法的优势
1.可读性强
没错,静态工厂方法可以起不同的方法名称,可读性强。而构造方法非常不幸的是,它必须和类名保持一致,导致阅读性差,甚至会在代码中调用错误的构造方法。
示例:
假设我们需要写一个产生随即数的类RandomIntGenerator,该类有两个成员属性:最小值min和最大值max,我们的需求是需要创建三种类型的RandomIntGenerator
对象:
1、大于min,小于max;
2、大于min 小于Integer.MAX_VALUE;
3、大于Integer.MIN_VALUE 小于max
如果使用传统方法去设计:
public class RandomIntGenerator
{
/**
* 最小值
*/
private int min = Integer.MIN_VALUE;
/**
* 最大值
*/
private int max = Integer.MAX_VALUE;
/**
* 大于min 小于max
* @param min
* @param max
*/
public RandomIntGenerator(int min, int max)
{
this.min = min;
this.max = max;
}
/**
* 大于min 小于Integer.MAX_VALUE
*/
public RandomIntGenerator(int min)
{
this.min = min;
}
// 报错:Duplicate method RandomIntGenerator(int) in type RandomIntGenerator
// /**
// * 大于Integer.MIN_VALUE 小于max
// */
// public RandomIntGenerator(int max)
// {
// this.max = max;
// }
}
观察以上代码,我们发现,以上代码不仅可读性差(new RandomIntGenerator(1, 10)与new RandomIntGenerator(10),不查文档,不看注释很难知道其创建的对象的具体含义),而且在设计最后一个构造方法的时候,还报错,因为已经存在一个参数一致的工作方法了,提示重复定义;
那么假设我们使用静态工厂方法会怎样呢,如下所示:
public class RandomIntGenerator
{
/**
* 最小值
*/
private int min = Integer.MIN_VALUE;
/**
* 最大值
*/
private int max = Integer.MAX_VALUE;
/**
* 大于min 小于max
* @param min
* @param max
*/
public RandomIntGenerator(int min, int max)
{
this.min = min;
this.max = max;
}
/**
* 大于min 小于max
* @param min
* @param max
*/
public static RandomIntGenerator between(int min, int max)
{
return new RandomIntGenerator(min, max);
}
/**
* 大于min 小于Integer.MAX_VALUE
*/
public static RandomIntGenerator biggerThan(int min)
{
return new RandomIntGenerator(min, Integer.MAX_VALUE);
}
/**
* 大于Integer.MIN_VALUE 小于max
*/
public static RandomIntGenerator smallerThan(int max)
{
return new RandomIntGenerator(Integer.MIN_VALUE, max);
}
}
成功满足需求:创建三种类型的RandomIntGenerator
对象,而且创建对象的时候,代码可读性比使用构造方法强;
2.调用的时候,不需要每次都创建一个新对象
这使得不可变类可以使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免创建不必要的重复对象。如果程序经常请求创建相同的对象,并且创建对象的代价很高时,则这项技术可以极高的提升程序性能。这样创建的类确保它是单例的,并且对应不可变类来说,确保不会存在两个相等的实例。
JDK中的Boolean类的valueOf方法可以很好的印证这个优势,在Boolean类中,有两个事先创建好的Boolean对象(True,False)
public final class Boolean implements java.io.Serializable,
Comparable<Boolean>
{
/**
* The {@code Boolean} object corresponding to the primitive
* value {@code true}.
*/
public static final Boolean TRUE = new Boolean(true);
/**
* The {@code Boolean} object corresponding to the primitive
* value {@code false}.
*/
public static final Boolean FALSE = new Boolean(false);
}
当我们调用Boolean.valueOf(“true”)方法时,返回的就是这两个实例的引用,这样可以避免创建不必要的对象,如果使用构造器的话,就达不到这种效果了;
public static Boolean valueOf(String s) {
return toBoolean(s) ? TRUE : FALSE;
}
3.可以返回此类的任何子类类型
多态的一种体现,确保类型的兼容与灵活性
// RedDog和BlackDog为Dog的子类
public static Dog getInstance(){
// 可以返回Dog的任何子类
return new RedDog();
}
4.创建带有泛型的方法时可以使代码更加简洁
例如:
// 创建带有泛型类型的Map
public class MyMap<K,V> {
/**
*
*/
public MyMap()
{
}
public static <K,V> MyMap<K,V> newInstance(){
return new MyMap<K, V>();
}
}
public class Main
{
public static void main(String[] args)
{
MyMap<String, String> map1 = new MyMap<String, String>();
// 更加简洁,不需要重复指明类型参数,可以自行推导出来
MyMap<String, String> map2 = MyMap.newInstance();
}
}
5.服务者提供框架思想
可以参考这个博主文章:https://www.jianshu.com/p/4d7a0cd36a82。
三、静态工厂方法带来的缺点
- 如果一个类的权限不是公开的,则不能实例化它的子类。换句话说就是私有化的构造函数,不能使用继承
- 一般程序员很难发现他们,方法特点没有构造器明显,并且和普通的静态方法没有什么太大区别
四、常用的命名方法
对于静态工厂方法命名,有一些约定俗成的用法。
-
from - 类型转换方法,返回该类型对应的实例,例如:
Data newDate = Date.from(instant) // 此方法不常用,这里只是用来举例说明命名格式
-
of - 聚合方法,带有多个实例,并返回该类型的实例,最经典的就是Stream流中的of方法
Stream<Integer> integerStream = Stream.of(1, 2, 3);
-
valueOf - 我们经常用到的方法,经常用于转换一个类型实例
// String 类别中已经提供了将基本数据型态转换成 String 的 static 方法 ,也就是 String.valueOf() String.valueOf(Object param); // 这个参数多载的方法 (1)String.valueOf(boolean b) : 将 boolean 变量 b 转换成字符串 (2)String.valueOf(char c) : 将 char 变量 c 转换成字符串 (3)String.valueOf(char[] data) : 将 char 数组 data 转换成字符串 (4)String.valueOf(char[] data, int offset, int count) : 将 char 数组 data 中 由 data[offset] 开始取 count 个元素 转换成字符串 (5)String.valueOf(double d) : 将 double 变量 d 转换成字符串 (6)String.valueOf(float f) : 将 float 变量 f 转换成字符串 (7)String.valueOf(int i) : 将 int 变量 i 转换成字符串 (8)String.valueOf(long l) : 将 long 变量 l 转换成字符串 (9)String.valueOf(Object obj) : 将 obj 对象转换成 字符串, 等于 obj.toString()
-
getInstance或者newInstance,即通过方法返回的实例,例如:
public class Person{ private Integer id; private String name; private String sex; private Integer age; public Person() { } public Person(Integer id, String name, String sex, Integer age) { this.id = id; this.name = name; this.sex = sex; this.age = age; } public static Person getInstance(Integer id, String name, String sex, Integer age) { return new Person(id,name,sex,age); } }
Person p1 = Person.getInstance(1001, "小明", "男", 20); Person p2 = Person.getInstance(1002, "小红", "女", 21);
五、使用Builder构建器来创建Beans
上述提到了使用静态工厂方法来代替构造方法。其实,在构建Java Beans 还可以通过建造者模式来构建类实例。它的使用场景在于:如果一个实体类非常复杂,它的字段参数非常多,通常,再去new对象,然后堆砌了大量的set代码,看起来非常不友好。并且,在一些实体类中可能有重叠构造器,即构造器依据参数个数存在多个。比如,有一个参数,就写有一个参数的构造器,有两个参数,再写有两个参数的构造器,由此叠加,一个实体类存在好多的构造器,非常冗余。
这时,建造者(Builder)模式就派上了用场,它可以使代码更加优雅。
示例:
public class Person{
private Integer id;
private String name;
private String sex;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
public static final class PersonBuilder {
private Integer id;
private String name;
private String sex;
private Integer age;
private PersonBuilder() {
}
public static PersonBuilder createPerson() {
return new PersonBuilder();
}
public PersonBuilder withId(Integer id) {
this.id = id;
return this;
}
public PersonBuilder withName(String name) {
this.name = name;
return this;
}
public PersonBuilder withSex(String sex) {
this.sex = sex;
return this;
}
public PersonBuilder withAge(Integer age) {
this.age = age;
return this;
}
public Person build() {
Person person = new Person();
person.setId(id);
person.setName(name);
person.setSex(sex);
person.setAge(age);
return person;
}
}
}
一个Person类中包含一个PersonBuilder内部类,当然了,这个PersonBuilder类也可以单独出来。我们看到,这个PersonBuilder类私有构造器,并且每个字段返回值都是这个类,最后通过build方法创建出真正的Person对象。这使得创建真正Person对象时代码更加简洁,因为它支持链式调用,并减少了冗余的构造器。
创建真正的Person的对象:
Person person = Person.PersonBuilder.createPerson().withId(1003).withAge(23).withName("测试").build();
当然使用构建器也会有缺点,为了创建对象 ,必须先创建它的构建器 ,虽然创建这个构建器的开销在实践中可能不那么明显 但,是在某些十分注重性能的情况下,可能就成问题了,因此它只在有很多参数的时候才使用,比如 4个或者更多个参数。
简而言之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时, Builder模式就是一种不错的选择, 特别是当大多数参数都是可选或者类型相同的时候,与使用重叠构造器模式相比,使用 Builder 模式的客户端代码将更易于阅读和编写,构建器也比JavaBeans 更加安全。