深入理解:Java静态工厂

Java 静态工厂方法详解

什么是静态工厂?:

在实际的开发中,我们经常会见到一种获取类实例的方法

    Fragment fragment = MyFragment.newIntance();
    // or 
    Calendar calendar = Calendar.getInstance();
    // or 
    Integer number = Integer.valueOf("3");

像这样的:不通过 new,而是用一个静态方法来对外提供自身实例的方法,即为我们所说的静态工厂方法(Static factory method)。

含义:

静态工厂方法值得是在类中提供一个公有的静态方法,返回类的一个实例。

优势:

一、它们有名字

由于语言的特性,Java 的构造函数都是跟类名一样的。这导致的一个问题是构造函数的名称不够灵活,经常不能准确地描述返回值,在有多个重载的构造函数时尤甚,如果参数类型、数目又比较相似的话,那更是很容易出错。

比如,如下的一段代码 :

Date date0 = new Date();
Date date1 = new Date(0L);
Date date2 = new Date("0");
Date date3 = new Date(1,2,1);
Date date4 = new Date(1,2,1,1,1);
Date date5 = new Date(1,2,1,1,1,1);

—— Date 类有很多重载函数,对于开发者来说,假如不是特别熟悉的话,恐怕是需要犹豫一下,才能找到合适的构造函数的。而对于其他的代码阅读者来说,估计更是需要查看文档,才能明白每个参数的含义了。

而如果使用静态工厂方法,就可以给方法起更多有意义的名字,比如前面的 valueOf、newInstance、getInstance 等,对于代码的编写和阅读都能够更清晰。

二、不用每次被调用时都创建新对象

有时候外部调用者只需要拿到一个实例,而不关心是否是新的实例;又或者我们想对外提供一个单例时 —— 如果使用工厂方法,就可以很容易的在内部控制,防止创建不必要的对象,减少开销。

三、可以返回原返回类型的子类

子类应该能替换父类。
显然,构造方法只能返回确切的自身类型,而静态工厂方法则能够更加灵活,可以根据需要方便地返回任何它的子类型的实例。

Class Person {
    
    
    public static Person getInstance(){
    
    
        return new Person();
        // 这里可以改为 return new Player() / Cooker()
    }
}
Class Player extends Person{
    
    
}
Class Cooker extends Person{
    
    
}

比如上面这段代码,Person 类的静态工厂方法可以返回 Person 的实例,也可以根据需要返回它的子类 Player 或者 Cooker。(当然,这只是为了演示,在实际的项目中,一个类是不应该依赖于它的子类的。但如果这里的 getInstance () 方法位于其他的类中,就更具有的实际操作意义了)

四、可以有多个参数相同但名称不同的工厂方法

构造函数虽然也可以有多个,但是由于函数名已经被固定,所以就要求参数必须有差异时(类型、数量或者顺序)才能够重载了。

例子:

class Child{
    
    
    int age = 10;
    int weight = 30;
    public Child(int age, int weight) {
    
    
        this.age = age;
        this.weight = weight;
    }
    public Child(int age) {
    
    
        this.age = age;
    }
}

Child 类有 age 和 weight 两个属性,如代码所示,它已经有了两个构造函数:Child(int age, int weight) 和 Child(int age),这时候如果我们想再添加一个指定 weight 但不关心 age 的构造函数,一般是这样:

public Child( int weight) {
    
    
    this.weight = weight;
}

但要把这个构造函数添加到 Child 类中,我们都知道是行不通的,因为 java 的函数签名是忽略参数名称的,所以 Child(int age)Child(int weight) 会冲突。

酱酱酱,这时候,静态工厂方法就可以登场了。

class Child{
    
    
    int age = 10;
    int weight = 30;
    public static Child newChild(int age, int weight) {
    
    
        Child child = new Child();
        child.weight = weight;
        child.age = age;
        return child;
    }
    public static Child newChildWithWeight(int weight) {
    
    
        Child child = new Child();
        child.weight = weight;
        return child;
    }
    public static Child newChildWithAge(int age) {
    
    
        Child child = new Child();
        child.age = age;
        return child;
    }
}

其中的 newChildWithWeightnewChildWithAge,就是两个参数类型相同的的方法,但是作用不同,如此,就能够满足上面所说的类似Child(int age)Child(int weight)同时存在的需求。

四、多了一层控制,方便统一修改

我们在开发中一定遇到过很多次这样的场景:在写一个界面时,服务端的数据还没准备好,这时候我们经常就需要自己在客户端编写一个测试的数据,来进行界面的测试,像这样:

    // 创建一个测试数据
    User tester = new User();
    tester.setName("隔壁老张");
    tester.setAge(16);
    tester.setDescription("我住隔壁我姓张!");
    // use tester
    bindUI(tester);
    ……

要写一连串的测试代码,如果需要测试的界面有多个,那么这一连串的代码可能还会被复制多次到项目的多个位置。

这种写法的缺点呢,首先是代码臃肿、混乱;其次是万一上线的时候漏掉了某一处,忘记修改,那就可以说是灾难了……

但是如果习惯了用静态工厂方法代替构造器的话,则会很自然地这么写,先在 User 中定义一个 newTestInstance 方法:

static class User{
    
    
    String name ;
    int age ;
    String description;
    public static User newTestInstance() {
    
    
        User tester = new User();
        tester.setName("隔壁老张");
        tester.setAge(16);
        tester.setDescription("我住隔壁我姓张!");
        return tester;
    }
}

然后调用的地方就可以这样写了:

    // 创建一个测试数据
    User tester = User.newTestInstance();
    // use tester
    bindUI(tester);

而且不只是代码简洁优雅,由于所有测试实例的创建都是在这一个地方,所以在需要正式数据的时候,也只需把这个方法随意删除或者修改一下,所有调用者都会编译不通过,彻底杜绝了由于疏忽导致线上还有测试代码的情况。

总结:

总体来说,我觉得『考虑使用静态工厂方法代替构造器』这点,除了有名字、可以用子类等这些语法层面上的优势之外,更多的是在工程学上的意义,我觉得它实质上的最主要作用是:能够增大类的提供者对自己所提供的类的控制力

作为一个开发者,当我们作为调用方,使用别人提供的类时,如果要使用 new 关键字来为其创建一个类实例,如果对类不是特别熟悉,那么一定是要特别慎重的 —— new 实在是太好用了,以致于它经常被滥用,随时随地的 new 是有很大风险的,除了可能导致性能、内存方面的问题外,也经常会使得代码结构变得混乱。

而当我们在作为类的提供方时,无法控制调用者的具体行为,但是我们可以尝试使用一些方法来增大自己对类的控制力,减少调用方犯错误的机会,这也是对代码更负责的具体体现。

おすすめ

転載: blog.csdn.net/qq_48642405/article/details/121831371