为什么说静态工厂方法可能会比构造器更好?听说读完就会有一个女朋友?


前言:相信读过《Effective Java》(第二版)的小伙伴知道我在说什么,作者在书中总结了78条黄金建议,其中建议的第一条就是:考虑用静态工厂方法替代构造器。可以说是开幕雷击,第一条我就懵X了,什么是静态工厂方法?为什么要替代构造器?是构造器不香吗?本文将根据这些疑问刨根问底,详细分析静态工厂方法的优劣,相信我,有问题欢迎评论指出,本篇文章一定不会枯燥,坚持读完哦~

在这里插入图片描述

什么是静态工厂方法

当我们想要获得一个类的实例对象的时候,恐怕一下子就通过new一个对象来获得了。那么静态工厂方法是怎么实现的呢?其实也很简单:

public static Girl newGirlFriend(boolean isBeautifulGirl) {
	return new Girl(isBeautifulGirl);
}

如此,我们通过类名加newGirlFriend就可以轻松地拿到对象。

所谓的静态工厂方法就是不通过new,而通过一个静态方法返回自身的实例对象

静态工厂方法比构造器好在哪里

由《Effective Java》可以得到四点静态工厂方法比构造器好的地方:
①静态工厂方法有名称而构造器没有(前者可读性更好)
②不必每次调用它们的时候都创建一个新对象
③可以返回原返回类型的任何子类对象(不局限于单一特定的类)
④创建泛型对象可以更加简洁

静态工厂方法有名称而构造器没有(前者可读性更好)

说到有名称这点,构造器是真的没法儿比:

public class GirlFriend {
    
    private boolean isBeautiful;
    private boolean isTall;
    
    public GirlFriend(){
        
    }
    public GirlFriend(boolean isBeautiful) {
        this.isBeautiful = isBeautiful;    
    }
    public GirlFriend(boolean isBeautiful, boolean isTall) {
        this.isBeautiful = isBeautiful;    
        this.isTall = isTall;    
    }
}

调用者如果不是非常熟悉这段代码,必须要看文档中有哪些重载的构造方法,稍有不慎,就会调错,毕竟构造方法都是一个名字,只有参数不同而已。但是静态工厂方法就不一样了,可以起很多能让调用者见名知意的名字:getBeautifulGirlfriend(); getTallGirl();,是不是看出差距了

另外:正是因为这一特性,所以哪怕他们有相同的参数,也不会报错,如果构造器有两个构造方法含有相同参数,必定出错,但是任性的静态工厂方法想怎么起名字就怎么起名字,完美避过了这个坑~
在这里插入图片描述

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

当我们需要获得类的实例对象的时候,为了性能,我们不在乎他是不是新对象,使用new必然会得到一个不同地址的对象(姑且称之为新对象),当我们使用new GirlFriend();这种方式直接创建对象,相当于对JVM说,我要一个对象,然后JVM会去根据双亲委派原则加载验证准备解析new一个对象发生了什么?),所以开支是蛮大的,尤其在重复新建一个对象的时候。小斌哥我之前的代码就都是这种毛病,并没有使用单例模式,每个方法都new同一个对象,我去,简直是伤害了我的CPU,心疼我的旧电脑,怪不得那么快就挂了(玩笑话);使用静态工厂方法最简单的一个例子就是单例模式,我们可以确保获取多个对象的时候只需要创建一次对象并且销毁一次对象,这样就很“环保”了,对内存的开支会达到最小,这点是构造器望尘莫及的。

说到单例模式,大家会想到“饿汉式”与“懒汉式”,举个最简单的例子不考虑线程安全给大家展示一下单例模式是如何做到多次获取对象只创建并销毁一次:
饿汉式:

public class Singleton{
    private static Singleton singleton = new Singleton ();
    private Singleton (){}
    public static Singleton getInstance(){
        return singleton;
}

懒汉式:

public class Singleton{
    private static Singleton singleton = null;
    public static Singleton getInstance(){
        if(singleton==null){
            singleton = new Singleton();
        }
        return singleton;
    }
}

使用单例模式不管创建多少次都会返回相同的对象,所以相当于对象被多次循环利用了,提升了效率。在这里插入图片描述

可以返回原返回类型的任何子类对象

这体现了里氏原则:任何基类可以出现的地方,子类一定可以出现。 比如有这样的UML:
在这里插入图片描述

public static Singleton getGirlFriend(){
        return new 女朋友();
        //可以返回新垣结衣()、长泽雅美()、松冈茉优()、绫濑遥()
        //因为都是女朋友的子类
}

这个优点有些欺负构造器了,毕竟它没有返回值。

创建泛型对象可以更加简洁

作者想表示过去泛型书写比较复杂,比如:

Map<String, List<String>> map = new HashMap<String, List<String>>(); 

额,但是jdk1.7之后这个问题已经木有了,毕竟这本书有一定年头了(第二版参考的是jdk1.6),所以就当这条没有吧。

另外,静态工厂方法还有其他好处,比如可以隐藏实现细节,只提供用户最简单的方法入口,比如让你写一个计算器软件,用户只需要输入:3*(7/1.455)+11-12,而方法的具体实现细节是私有的,用户是看不到的,也不需要看到,否则就体现不了封装的特性了,一个是用户使用着麻烦,另外是出错的可能性更大,你暴露给用户更多的参数更多的方法,出BUG的可能性就更大。这就好比现在的手机,只有关机键、音量键,如果把手机后盖拆开暴露给你,手机被整坏的可能性必然会提升啊。。

我们熟知的工具类Collections就是一个很好的例子,他把构造器设置为私有,所以调用者不能通过new来调用类中的方法,只能通过类名加方法名去调用,并且内部需要计算的方法都设为private,调用者看不到也不需要去知道,比如sort方法和binarySearch等。

静态工厂的局限之处

凡事都是两面的,虽然静态工厂方法很好,但是它只要构造方法私有化了,就不能被实例化,也就是说,想让拒绝访问构造器的类拥有子类是不可能的(就像做了绝育手术)。第二点就是它跟普通的静态方法没啥两样,虽然我们管它叫静态工厂方法,但是它依然只是一个很简单的静态方法而已,并不像构造器,有自己的专属名称,还可以在API文档中看到他的名字。但是静态工厂方法也会有一些不成文的规定,正如我们会看到一些newInstance()、valueOf()、getInstance()等,这些往往就是静态工厂方法实现的。

总结

作者给我们这条建议,并非让我们用静态构造方法彻底替换构造器,只是希望我们在以后可以有多一种选择,直接new并不是唯一的方法,他们各有各的好,需要我们去进行合适的选择。

原文地址:https://www.cnblogs.com/taobean/p/12364237.html