第一条:用静态工厂方法代替构造器

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

首先,要明白构造器的作用是什么,而静态工厂代替了它什么。众所周知,构造器是和类名同名的一个特殊的类方法,特殊之处在于它不需要指定返回值类型,构造器的作用就是实例化一个对象。静态工厂方法代替的就是构造器的这个作用。

比如这里

 Integer i = Integer.parseInt("1");

我们使用Integer下的一个静态方法创建了一个Integer对象,而不是采用new的方式。通过这个例子并不能看出静态工厂方法有什么优点和缺点,那么,接下来分析一个静态工厂方法的优点。

静态工厂方法的优点

静态工厂方法拥有更具体的名称

对于BigInteger这个类,拥有构造器BigInteger(int, int , Random),可以用它来产生一个数,而如果想直接让这个方法返回一个素数,可以用该方法下的BigInteger.probablePrime,这样子能够让调用者更直观我们产生了一个怎么样的对象。

举个例子

public class Person {
    private String gender;
    
    private Person(String gender) {
        this.gender = gender;
    }
    
    public static Person woman() {
        return new Person("male");
    }
    public static Person man() {
        return new Person("man");
    }
}

这个类中有一个私有的构造器,因此唯一能够获取该类的实例只能调用两个静态方法,而通过两个静态方法的名字,我们能够很清楚的知道返回的是一个“男人”还是一个“女人”。

返回相同的对象

实例受控的类(instance-controlled),简单讲就是能够随时提供相同的对象,实例受控可以确保它是一个单例或者说是不可实例化的。

这种方法类似于享元模式,如果程序经常创建”一样“的对象且创建的开销很大时,采用静态工厂方法就可以极大的提高性能。在Spring框架开发Web的情况下,我们无论是连接一个MySQL,Oracle,还是Redis,Zookeeper等,都需要维持一个Connection,而这个Connection就像是一条高速公路,需要传递的信息就像是路上的车,我们没有必要为每辆车的通行修一条路,换句话说修一条路的代价是十分高的。所以,在Spring中,框架自动的将Connection维护为一个全局的单例对象。

举个例子,要求每次返回的person都必须是一样的

public class Person {
  
    private Person() {}
    
    private static Person person = new Person();
    
    public Person getPerson() {
        return person;
    }
}

这个类中,我们首先将的构造器给私有化了,表示我们不希望这个类被其他类实例化。同时我们提供了一个静态工厂方法,返回一个已经被我们实例化好的Person对象,而这个对象在这个类中维护的是同一个对象,因此,我们就保证了其单例的性质。

可以返回任何子类型或者实现

该优点是建立在面向对象的”多态“前提基础上,这项技术适用于基于接口的框架

举个例子,我们想让让两个静态工厂方法返回一个交通工具,且这一个交通工具以不同的方式运行。这句话非常拗口,说白了就是返回的类型是一样的,但是做同样的事情有不同的效果。

public abstract class Vehicle {

    protected abstract void drive();

    private static class Car extends Vehicle {
        @Override
        protected void drive() {
            System.out.println("on the road");
        }
    }

    private static class Plane extends Vehicle {

        @Override
        protected void drive() {
            System.out.println("fly in the sky");
        }
    }

    public static Vehicle getACar() {
        return new Car();
    }

    public static Vehicle getAPlane() {
        return new Plane();
    }

    public static void main(String[] args) {
        Vehicle car = Vehicle.getACar();
        Vehicle plane = Vehicle.getAPlane();
        car.drive();
        plane.drive();
    }
}

很显然,这个方法的控制台输出为

on the road
fly in the sky

是不是有点感觉,在我们使用的很多工具都是这样子,比如我们使用的slf4j日志门面框架,通过所谓的协调器,就可以将不同的日志实现框架,通过slf4j暴露出来。这种统一暴露,隐藏实现的模式非常有趣和重要,在开发时要多悟。

具体实现可以随时更换而不影响系统

举一个场景,有一个静态工厂方法返回一个交通工具,目前它返回的是一个继承自Vehicle的Plane,但是现在系统升级,我们希望飞机升级为战斗机,我们可以直接这样做

public static Vehicle getAPlane() {
        //return new Plane();
        return new WarCraft();  // WarCraft extends Vehicle
}

这样子,我们可以在不改动系统的其他的任何地方做到平滑的代码升级。这种应用场景有,当我们将第三方功能引入我们的项目时,为了减少对我们项目的侵入性,我们可以使用静态工厂方法返回一个本项目的类(处理过第三方功能)。往后,假设需要更换第三方功能实现,就可以仅仅修改静态工厂方法。

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

这种静态工厂方法构成了服务提供者框架

服务提供者框架:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来

简单来说,就是服务的实现,服务本身,客户端时完全分开的。

任何一个所谓的服务,都需要保证客户端能够调用服务,服务提供者可以去注册服务这两个基本功能,因此,对于这种模式,又是如何实现这两点的呢?

服务提供者框架有三个重要角色:服务接口,提供者注册API,服务访问API,顾名思义,服务注册API就是提供者注册服务的方式,服务访问API就是使用服务接口的方式,而他们中间的桥梁就是服务接口。这里的接口不是Interface,只是一种抽象的概念

同时为了让服务方便的注册服务,还可以使用第四个组件:服务提供者接口。是个服务提供者使用的,通过该接口去调用提供者注册API,进入注册服务,如果不存在该组件,服务提供者可以使用反射的方式注册自己。

举个非常熟悉的例子,JDBC。

forName("com.jdbc.mysql.Driver");

这句代码在学习JDBC开始的时候经常见到,这就是通过反射的方式注册服务。而它注册的时什么服务呢?

DrvierManager.registryDrivrer();

这下我们就明白了,它注册了一个驱动,而这里的DriverManager就是组件之一的服务接口。但是,讲到这里是不是还是没有讲到静态工厂方法的事情?接下来就是

private static Connection getConnection(
       String url, java.util.Properties info, Class<?> caller) throws SQLException {
       ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
       synchronized(DriverManager.class) {
           // synchronize loading of the correct classloader.
           if (callerCL == null) {
               callerCL = Thread.currentThread().getContextClassLoader();
           }
       }
       if(url == null) {
           throw new SQLException("The url cannot be null", "08001");
       }
       println("DriverManager.getConnection(\"" + url + "\")");
       SQLException reason = null;
    // 这里!!!!!!!!!!!!!!!!!!!!!
       for(DriverInfo aDriver : registeredDrivers) {
           if(isDriverAllowed(aDriver.driver, callerCL)) {
               try {
                   println("    trying " + aDriver.driver.getClass().getName());
                   Connection con = aDriver.driver.connect(url, info);
                   if (con != null) {
                       // Success!
                       println("getConnection returning " + aDriver.driver.getClass().getName());
                       return (con);
                   }
               } catch (SQLException ex) {
                   if (reason == null) {
                       reason = ex;
                   }
               }

           } else {
               println("    skipping: " + aDriver.getClass().getName());
           }

       }

这个静态方法中的registeredDrivers很明显不是提前预设,喜欢看源码的朋友可以发现DriverManager类加载的时候就尝试去获取可能被注册驱动。

总而言之,驱动是开发的时候根据数据库的类型导入的,而开发的时候我们却没有注册任何驱动,这就是所谓的静态工厂方法返回的类,在其编写是可以不存在。

静态工厂方法的缺点

1.静态工厂方法提倡不实例化,而这带来的问题就是不能子类化。

2.API的数量暴增,不易记住。每种返回的对象都设置一个静态方法,那么方法的数量肯定是非常多的。

git地址 https://github.com/JayHooooooo/effectiveJava ,点个star吧,这将激励我创作!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342