Java中级——用静态工厂方法代替构造器

静态工厂方法和构造器是什么?

  • 构造器即构造函数,用于生成类实例
  • 静态工厂方法不同于设计模式中的工厂方法,而是一个返回类实例的静态方法

优点1——更利于理解

  • 原文:如果构造器的参数本身没有确切地描述正在被返回的对象,那么具有适当名称的静态工厂会更容易使用,产生的代码也更易于阅读

如一个输入数字,返回其是奇数还是偶数的字符串,利用构造器的设计如下

class A {
    private String value;

    A(int num) {
        if (num % 2 == 0) {
            value = "even";
        } else {
            value = "odd";
        }
    }

    public String getValue() {
        return value;
    }
}

而利用静态工厂方法的设计如下

class B {
    public static String parityOf(int num) {
        if (num % 2 == 0) {
            return new String("even");
        } else {
            return new String("odd");
        }
    }
}

可看到类A中参数mum并不能指明待返回对象是什么,这会导致用途不明确,而类B的静态方法则更加明确的指出了其用途

优点2——可打破构造器签名的限制

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

如下,对于具有int、int、String域的类C来说,不能同时有两个签名C_int_String(即代码中第二个构造器),然而这个限制可通过调整参数顺序打破(即代码中的第三个构造器),但这样会增加复杂性和不易理解

class C {
    private int id;
    private int age;
    private String name;

    C(int id, String name) {

    }

    /*C(int age, String name) {

    }*/

    C(String name, int age) {

    }
}

而具有方法名的静态工厂方法则此不受限制

class D {
    private int id;
    private int age;
    private String name;

    public static D initIDAndName(int id, String name) {
        D d = new D();
        d.id = id;
        d.name = name;
        return d;
    }

    public static D initAgeAndName(int age, String name) {
        D d = new D();
        d.age = age;
        d.name = name;
        return d;
    }
}

优点3——不必重复创建对象

  • 原文:不可变类可以使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免创建不必要的重复对象

如上述判断奇偶数的程序,构造器的改进设计如下

final class E {
   private static final String ODD = "odd";
   private static final String EVEN = "even";
   private String value;

   E(int num) {
       if (num % 2 == 0) {
           value = EVEN;
       } else {
           value = ODD;
       }
   }

   public String getValue() {
       return value;
   }
}

但仍然需要对重复创建value,而使用静态工厂方法则可以直接返回创建好的对象

final class F {
    private static final String ODD = "odd";
    private static final String EVEN = "even";

    public static String parityOf(int num) {
        if (num % 2 == 0) {
            return EVEN;
        } else {
            return ODD;
        }
    }
}

优点4——可以限制实例存在

  • 原文:静态工厂方法能够为重复的调用返回相同的对象,这样有助于类总能严格控制在某个时刻哪些实例应该存在

限制1——可以限制类是一个单例(每次返回都是同一对象)

class G {
    G() {

    }
}

class H {
    private static H hInstance = new H();

    private H() {

    }

    public static H getInstance() {
        return hInstance;
    }
}

限制2——限制类不可实例化(如上述F是工具类,可对其构造器私有化,避免创建F2导致重复的odd和even)

class F2 {
    private static final String ODD = "odd";
    private static final String EVEN = "even";

    private F2() {
    }

    public static String parityOf(int num) {
        if (num % 2 == 0) {
            return EVEN;
        } else {
            return ODD;
        }
    }
}

同时限制2可以让存在final域的类不会存在两个相等的实例

优点5——可返回子类

如下构造器只能返回它本身的实例,而静态工厂方法可返回其子类

class J {
    public static J create() {
        return new K();
    }
}

class K extends J {

}

进阶——可根据参数返回不同的子类型

class L {
    public static L create(int num) {
        switch (num) {
            case 1:
                return new M();
            default:
                return new N();
        }
    }
}

class M extends L {

}

class N extends L {

}

优点6——返回的类可延迟创建

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

有点难以理解,主要是用于原文中提到的服务提供者框架,列如java提供一个统一的标准框架给供应商,如下(编写getService时还不知道返回的是什么服务,这就是延迟创建

interface Service {
    void beginService();
}

interface ServiceProvider {
    Service getService();
}

class ServiceManager {

    private static final Map<String, ServiceProvider> providers = new HashMap<>();

    public static void registerProvider(String name, ServiceProvider provider) {
        providers.put(name, provider);
    }

    public static Service getService(String name) {
        ServiceProvider p = providers.get(name);

        if (p == null) {
            throw new IllegalArgumentException("No provider registered with name:" + name);
        }
        return p.getService();
    }
}

供应商(这里举例为医院)提供一个服务(这里举例为养老服务),需要实现Service和ServiceProvider,并让ServiceProvider到ServiceManager去注册,如下

class oldService implements Service {

    @Override
    public void beginService() {
        System.out.println("即将为你进行养老服务");
    }
}

class XXXHospital implements ServiceProvider {

    static {
        ServiceManager.registerProvider("XXXHospital", new XXXHospital());
    }

    @Override
    public Service getService() {
        return new oldService();
    }
}

而客户要使用供应商提供的服务,则需通过ServiceManager获取服务(需要先加载服务,并通过关键字XXXHospital找到对应的供应商),如下

try {
    Class.forName("com.demo.demo0.XXXHospital");
    Service xxxHospital = ServiceManager.getService("XXXHospital");
    xxxHospital.beginService();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

这也正是java为数据库提供商和用户之间建立链接的方式,如下为连接jdbc代码

Class.forName("com.mysql.jdbc.Driver"); 
DriverManager.getConnection("jdbc:mysql://localhost:3306/test","root","123");  

其他关于服务提供者框架的介绍可查看这篇文章https://www.iteye.com/blog/liwenshui322-1267202

缺点1——不含public或defualt构造器就不能被子类实例化

子类在调用构造器时需要调用父类的构造器,若父类不含public或defualt构造器就不能被子类实例化,但可通过子类复合父类的方式对父类进行构造

class O {
    private O() {

    }

    public static O create() {
        return new O();
    }
}

class P {
    private O o;

    P() {
        o = O.create();
    }
}

缺点2——不容易发现

一般人都是利用构造器创建实例并调用方法,可能并不知道存在静态工厂方法,常用的静态工厂方法命名为

  • from——类型转换
  • of——参数合并
  • instance或getInstance——单例
  • create或newInstance——创建一个实例
  • getType、newType或type——返回类型
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值