01.考虑使用静态工厂方法替代构造方法

前言

《Effective Java》中文第三版,是一本关于Java基础的书,这本书不止一次有人推荐我看。其中包括我很喜欢的博客园博主五月的仓颉,他曾在自己的博文《给Java程序猿们推荐一些值得一看的好书》中也推荐过。加深自己的记忆,同时向优秀的人看齐,决定在看完每一章之后,都写一篇随笔。如果有写的不对的地方、表述的不清楚的地方、或者其他建议,希望您能够留言指正,谢谢。

《Effective Java》中文第三版在线阅读链接:https://github.com/sjsdfg/effective-java-3rd-chinese/tree/master/docs/notes

什么是构造方法?

定义:

  • 一个在创建对象时自动被调用的方法。

特点:

  • 构造方法的名称和类同名。
  • 没有返回值类型,即不能使用 return 关键字。
  • 普通方法不能以任何形式调用构造方法(构造方法中可以调用普通方法)。

注意:

  • 当类中没有定义构造方法时,系统会默认添加一个无参的构造方法,当在类中定义构造方法的时候,默认的构造方法会消失。

实例:

public class Cat {
    //名字
    private String name;

    //颜色
    private String color;

    //年龄
    private Integer age;

    //构造方法的名称和类同名
    public Cat(){

    }

    //带一个参数的构造方法
    public Cat(String name){
        this.name = name;
        //普通方法不能以任何形式调用构造方法,构造方法中可以调用普通方法
        commonMethod();
    }

    //有两个参数的构造方法
    public Cat(String name, String color){
        this.name = name;
        this.color = color;
    }

    public void commonMethod(){
    }
}

什么是静态工厂方法?

定义:

  • 在类中提供一个公共的静态方法,通过这个静态方法,对外提供自身实例。

实例:

public class Cat {
    //名字
    private String name;

    //颜色
    private String color;

    //年龄
    private Integer age;
    
    //创建只有名字的猫,带有一个构造参数
    public static Cat createCatWithName(String name){
        Cat cat = new Cat();
        cat.name = name;
        return cat;
    }
    
    //创建只有颜色的猫,带有一个构造参数
    public static Cat createCatWithColor(String color){
        Cat cat = new Cat();
        cat.color = color;
        return cat;
    }
    
    //创建有名字、颜色、年龄的猫
    public static Cat createCatWithAllParam(String name, String color, Integer age){
        Cat cat = new Cat();
        cat.age = age;
        cat.name = name;
        cat.color = color;
        return cat;
    }
}

为什么考虑使用静态工厂方法替代构造方法?(静态工厂方法的优点)

  • 与构造方法不同,静态工厂方法可以有多个入参数量相同,但名称不同的方法。

     实例:请参考上方的两个实例

  • 与构造方法不同,静态工厂方法都是有名字的。生成的代码更易于阅读,也能突出它们的差异。

     实例:

public class Test02 {

    private String s1;

    private String s2;

    private String s3;

    private Integer i1;

    public Test02(){
    }

    public Test02(String s1){
        this.s1 = s1;
    }

    public Test02(String s1, String s2){
        this.s1 = s1;
        this.s2 = s2;
    }

    public Test02(String s1, Integer i1){
        this.s1 = s1;
        this.i1 = i1;
    }

    public Test02(String s1, String s2, String s3){
        this.s1 = s1;
        this.s2 = s2;
        this.s3 = s3;
    }

    public static Test02 createTest02InitAParam(String s1){
        Test02 result = new Test02();
        result.s1 = s1;
        return result;
    }

    public static Test02 createTest02InitTwoParam(String s1, String s2){
        Test02 result = new Test02();
        result.s1 = s1;
        result.s2 = s2;
        return result;
    }

    public static Test02 createTest02InitThreeParam(String s1, String s2, String s3){
        Test02 result = new Test02();
        result.s1 = s1;
        result.s2 = s2;
        result.s3 = s3;
        return result;
    }

    public static void main(String[] args) {
        //构造方法创建对象
        //前面我们说过,构造方法的名称和类同名,这导致构造函数的名称不够灵活,不能准确的描述返回值。
        //如果参数类型、数目比较相似的话,更加不容易找到合适的构造函数。例如test02与test04。
        Test02 test0 = new Test02();
        Test02 test01 = new Test02("A");
        Test02 test02 = new Test02("A","B");
        Test02 test03 = new Test02("A","B","C");
        Test02 test04 = new Test02("A", 1);

        //静态工厂方法创建对象
        //这让程序员更好的记得调用哪个方法创建适合自己需要的实例。注意仔细选择名称来突出它们的差异,这里我只是随便取的名字。
        Test02 aParam = Test02.createTest02InitAParam("A");
        Test02 twoParam = Test02.createTest02InitTwoParam("A", "B");
        Test02 threeParam = Test02.createTest02InitThreeParam("A", "B", "C");
    }

}
  • 与构造方法不同,静态工厂方法不用每次调用时都创建一个新对象。使用预先构建的实例,反复分配它们避免了不必要的重复。这个特点在创建新对象的代价非常昂贵的情况下,是可以极大地提高性能的。

     实例:

public static final Boolean TRUE = new Boolean(true);

public static final Boolean FALSE = new Boolean(false);

public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
  • 与构造方法不同,静态工厂方法可以返回原返回类型的子类。例如一个应用是API,这让它可以返回对象而不需要公开它的类,这种方式隐藏类会使得API非常紧凑。

  实例:

Class Person {
    public static Person getInstance(){
        return new Person();
        // return new Son01() / Son02()
    }
}
Class Son01 extends Person{
}
Class Son02 extends Person{
}
  • 与构造方法不同,静态工厂方法可以入参值的范围。作为类的提供者,类入参的范围越大,调用者就越容易出错。下方实例中,调用者只用传入type即可调用。当提供者期望传入构造函数的值是事先定好的值时(下方TYPE_A、TYPE_B),但如果不是,就很容易导致程序错误。避免这种错误,我们可以使用静态工厂模式。调用方无须知道也无需制定type值,这样就能控制type赋值的范围。

  实例:

public class Test03 {
    public static final int TYPE_A = 1;
    public static final int TYPE_B = 2;
    public static final int TYPE_C = 3;
    int type;

    private Test03(int type) {
        this.type = type;
    }

    public static Test03 newA() {
        return new Test03(TYPE_A);
    }
    public static Test03 newB() {
        return new Test03(TYPE_B);
    }
    public static Test03 newC() {
        return new Test03(TYPE_C);
    }

    public static void main(String[] args) {
        Test03 test03 = newA();
    }
}

静态工厂方法的缺点

  • 类如果不含有publicprotected的构造器,就不能被子类化。原因是父类缺少公有的构造方法,而子类无法调用父类的私有构造方法,导致子类无法生成构造方法。

     实例:

public class Person {
    private String name;
    private Integer age;

    private Person(String name, Integer age){
        this.name = name;
        this.age = age;
    }

    public static Person getSon01(String name, Integer age){
        return new Person(name, age);
    }

    public static Person getSon02(String name, Integer age){
        return new Person(name, age);
    }
    
   //此时Student无法创建,因为Person没有公共的构造方法
    public class Son extends Person{
        
    }

}

总结


静态工厂方法,语法层面

  • 能够有自己的名字
  • 用子类使代码更加紧凑。

性能的层面

  • 避免创建大量等价的实例对象。

最重要的是,当我们作为类的提供者,能够更好的控制调用者的具体行为,减少调用方出错的行为。我认为这也是对自己代码负责的体现。

原文地址:https://www.cnblogs.com/gongguowei01/p/12147738.html