天天看点

Java最佳实践经验第1条:用静态工厂方法代替构造器

用静态工厂方法代替构造器

      • 优势
        • 1、静态工厂方法有名称
        • 2、不必每次调用它们的时候都创建一个新对象
        • 3、可以返回原返回类型的任何子类型
        • 4、所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值
        • 5、方法返回的对象所属的类,在编写包含该静态工厂方法时候的类时可以不存在
      • 说完了好处,来讲讲缺点
        • 1、静态工厂方法最主要的缺点在于,类如果不包含公有的或被保护的构造器,就不能被子类实例化
        • 2、第二个缺点在于,程序员很难发现这些静态方法
      • 静态工厂方法的惯用名称
      • 总结

首先举个例子:

class LazyMan{
    //1、   创建一个私有的静态的指向自己的变量
    private static LazyMan instance;

    //2、   私有化构造器,如果是第一次调用将创建对象实例,以后就不会再次创建
    private LazyMan() {
        System.out.println("-------创建了一个懒汉式LazyMan对象------");
    }

    //3、   创建一个公共的静态方法,
    public static LazyMan getInstance() {
        if(instance == null) {
            instance = new LazyMan();
        }
        return instance;
    }
}
           

这是个懒汉式的单例设计,他没有共有的构造器,对外只开放了一个

getInstance

的方法,这个方法的返回值类型是其本身,在其他类中需要使用

LazyMan

的对象时,只需要通过调用

LazyMan.getInstance()

,这就是一个典型的用静态工厂方法代替构造器的案例实践。那么这种方式有一些什么特点呢?唯物主义价值观告诉我们,要辩证地看待一件事情,敲代码也是一样的,正如没有完美的编程语言一般,再优秀的设计思想也有其糟粕之处。

优势

1、静态工厂方法有名称

储备知识:获取方法签名

javap -s 包名.类名

,方法签名中带有参数类型和方法名

Java最佳实践经验第1条:用静态工厂方法代替构造器

当一个类需要多个带有相同签名的构造器时,就可以用静态工厂方法代替构造器,并且仔细地选择名称以突出静态工厂方法之间的区别。

2、不必每次调用它们的时候都创建一个新对象

这个也很好举例子,典型的应用就是在单例设计中,直接建好了一个对象作为类中的成员属性放着,等调用

getInstance()

方法的时候把已经创建的对象传出去,这样永远都只有一个对象在使用。熟悉

Spring

框架的小伙伴应该记得Spring的IOC容器管理也是默认用的单例模式,同时只存有一个对象,不必每次调用它们的时候都创建一个新对象。

这种方式还有个好处:可以使得不可变类(在后面的博客里详细讲)可以使用预先构建好的对象,还记得

Java5

版本中新增的包装类么?在Boolean包装类中就有这么一条:

public static Boolean valueOf(boolean b) {
	   return b ? Boolean.TRUE : Boolean.FALSE;
}
           

就用到了这种思想:它没有创建对象,而是用了预先构建好的对象,我们来看下源码:

Java最佳实践经验第1条:用静态工厂方法代替构造器

其原理很类似于享元模式

(挖个坑,后面补)

3、可以返回原返回类型的任何子类型

以这种形式隐藏实现类会使得API更加简洁,很适用于

基于接口的框架

。这个很好理解:能使用父类的地方都可以使用其子类替代——里氏替换原则

(挖个坑。后面补五大设计原则和一大设计法则)

。no speaking,just coding,让我们来写一个demo试一下:

随便写个接口

public interface Fruit {
    
}
           

来个类实现接口,用接口的类型来作为静态工厂方法的返回值类型,这里要把构造器私有化防止外部调用

public class Apple implements Fruit{
    private String color;
    private int size;
    private Apple(String color, int size) {
        this.color = color;
        this.size = size;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "color='" + color + '\'' +
                ", size=" + size +
                '}';
    }

    public static Fruit getInstance(String color, int size){
        return new Apple(color,size);
    }
}
           

在demo里调用getInstance方法,看看能否用子类型去接收

public class Demo1 {
    public static void main(String[] args) {
        Apple apple = (Apple) Apple.getInstance("红色", 100);
        System.out.println(apple);
    }
}
           
Java最佳实践经验第1条:用静态工厂方法代替构造器

4、所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值

这句话有2层含义:第一,返回对象的类可以是已声明的返回类型的所有子类型,这都是允许的。第二,方法的重载于返回值类型无关,我们可以定义多个不同参数的静态工厂方法,对应返回不同的对象。

5、方法返回的对象所属的类,在编写包含该静态工厂方法时候的类时可以不存在

听起来比较绕,其实很好理解,这一条对应了前2条,举个例子就明白了,比如我现在写了一个接口

Fruit

,在接口里声明了一个静态工厂方法:

public interface Fruit {
    static Fruit getInstance(String color, int size) {
        return null;
    }
}
           

这里有个点要提醒下:在java8之前接口中不能存在

static

关键字,通常采用

将静态方法放在一个不可实例化的伴生类

中的形式加以存储。最典型的例子就是

Java Collections Framework

,所有对集合接口的工具实现都是通过静态工厂方法在一个不可实例化的类

java.util.Collections

中导出。

让我们回来继续之前的话题,在写

Fruit

接口的时候,我还没有想好要用什么类来实现它,现在我想好了,我们让一个

Apple

类来实现

Fruit

接口:

public class Apple implements Fruit{
    private String color;
    private int size;
    private Apple(String color, int size) {
        this.color = color;
        this.size = size;
    }

    @Override
    public String toString() {
        return "Apple{" +
                "color='" + color + '\'' +
                ", size=" + size +
                '}';
    }

    public static Apple getInstance(String color, int size){
        return new Apple(color,size);
    }
}
           

检验结果,同样可以正常返回一个

Apple

类的对象:

public class Demo1 {
    public static void main(String[] args) {
        Apple apple = Apple.getInstance("红色", 100);
        System.out.println(apple);
    }
}
//输出
Apple{color='红色', size=100}
           

这大大增加了灵活性,并且符合

开闭原则

说完了好处,来讲讲缺点

1、静态工厂方法最主要的缺点在于,类如果不包含公有的或被保护的构造器,就不能被子类实例化

例如要想将

Collection Framework

中的任何便利的实现类子类化,这是不可能的。但是这样也许会因祸得福,因为它鼓励了程序员使用复合(组合)(composition),而不是继承,这正是不可变类型所需要的,也防止了继承的滥用。

2、第二个缺点在于,程序员很难发现这些静态方法

在API文档中,它们没有像构造器那样在API文档中明确标识出来,因此对于提供了静态工厂方法而不是构造器的类来说,要想查明如何实例化一个类是非常困难的。Javadoc工具总有一天会注意到静态工厂方法。同时我们现在可以通过遵守标准的命名习惯,来弥补这一劣势。下面提供了一些惯用名称。

静态工厂方法的惯用名称

1、from:类型转换方法,它只有单个参数,返回该类型的一个相对应的实例,例如:

Date d = Date.from(instant);

2、of:聚合方法,带有多个参数,返回该类型的一个实例病合并,例如:

Set<Rank> faceCards = EnumSet.of(JACK,QUEEN,KING);

3、valueOf:比from和of更繁琐的一种替代方法,例如:

BigInteger prime = BigInteger .valueOf(Integer.MAX_VALUE);

4、instance或者getInstance:返回的实例是通过方法的参数来描述的(如有),但是不能说与参数具有相同的值,例如:

StackWalker luke = StackWalker .getInstance(options);

5、create或者newInstance:像instance或者getInstance一样,但create或者newInstance能够确保每次调用都返回一个新的实例,例如:

Object newArray = Array.newInstance(classObject,arrayLen);

6、getType:像getInstance一样,但是在工厂方法返回值处于不同的类中的时候使用,Type表示工厂方法所返回的对象类型,例如:

FileStore fs = Files.getFileStore(path);

7、newType:像newInstance一样,但是在工厂方法返回值处于不同的类中的时候使用,Type表示工厂方法所返回的对象类型,例如:

BufferReader br = File.newBufferedReader(path);

8、type:getType和newType的简化版,例如:

List<Complaint> litany = Collections.list(legacyLitany);

总结

静态工厂方法和公有构造器各有用处,我们需要理解他们各自的长处。静态工厂经常更加合适,因此切忌第一反应就是提供公有的构造器,而不先考虑静态工厂。