如何优雅的使用静态工厂方法代替构造器

对于类而言,为了让客户端获取它自身的一个实例,最传统的方法就是提供一个公有的构造器。还有一种方法,类可以提供一个公有的静态工厂方法,这个方法是一个只返回类实例的静态方法。本文将介绍使用静态工厂方法的好处。


1、静态工厂方法与构造器不同的第一大优势在于,它们有名称

一个类可以有多个构造器,这些构造器签名相同,它们的区别仅在于参数个数或参数的顺序不同。对于这些构造器,在没有相应的参考文档时,往往不知道该选择使用哪个构造器。而对于静态方法来说,由于它们都有自己的方法名称,所以不受上述限制。当一个类需要多个带有相同签名的构造器时,就用静态工厂方法代替构造器,并且仔细地选择名称以便突出静态工厂方法之间的区别。
下面以线程池创建为例,通过构造器的方式创建一个线程池的构造方法如下:

 public ThreadPoolExecutor(int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           ThreadFactory threadFactory,
                           RejectedExecutionHandler handler) {
         此处省略
                           }

 public ThreadPoolExecutor(int corePoolSize,
                           int maximumPoolSize,
                           long keepAliveTime,
                           TimeUnit unit,
                           BlockingQueue<Runnable> workQueue,
                           RejectedExecutionHandler handler) {
     此处省略
                           }

看到了么,这种参数特别多的构造方法,是不是贼恐怖,假设张三想创建一个单线程线程池,但是他又不知道这构造方法参数什么意思或者不清楚这两个构造方法有什么区别,连选择用哪个都是问题,该怎么办呢?
这时我们就可以给他提供一个静态工厂方法,创建的细节他不需要知道,这个静态工厂方法名称也可以很明确的告诉他创建的是什么类型的对象。就像这样:

public static ThreadPoolExecutor newSingleThreadExecutor() {
    retrun new ThreadPoolExecutor(………);
}

这样张三在想创建单线程线程池时,只需要通过ThreadPoolExecutor.newSingleThreadExecutor()这种方法就可以创建一个单线程线程池了,就会变得简单明了了。(上述代码是我为了方便理解而对有所变更,和实际有所差别,但是意思确是一个意思)

因此,在面对这样api时,我想没有人会不喜欢这种静态工厂方法创建对象的方式吧。

2、静态工厂方法的第二大优势在于,不必每次调用它们的时候都创建一个新对象

静态工厂方法能够为重复的调用返回相同的对象,这样有助于类总能严格控制在某个时间哪些实例应该存在。如果程序经常请求创建相同的对象,并且创建对象的代价很高,通过静态工厂方法可以控制返回预先构建好的实例对象或者将构建好的对象缓存起来,从而避免创建不必要的对象,可以降低内存空间的占用和提高性能。

这种静态工厂方法在实际工作中经常用到,这里就不举例子了。

3、静态工厂的第三大优势在于,它们可以返回原返回类型的任何子类型的对象。

这种一般用于基于接口的框架中,可以灵活的选择返回对象的类,并且接口为静态工厂的方法提供了自然返回类型。
下面举一个简单的例子,有一个人员抽象类People,里面有一个获取出行方式的抽象方法,它有两个实现类,一个是有钱人类RichPeople,一个是穷人类PoolPeople,如下:

/**
 * 人的抽象类
 */
public abstract class People {
    //出行方式
    public abstract String travelType();
}

/**
 * 实现类 穷人类
 */
public class PoolPeople extends People{

    @Override
    public String travelType() {
        return "自行车";
    }
}

/**
 * 实现类 富人类
 */
public class RichPeople extends People{

    @Override
    public String travelType() {
        return "直升机";
    }
}

现在有以下需求,某个地方传过来一个bool类型的有钱人标识,根据这个标识获取对应的出行方式
正常我们的做法应该是在方法里判断这个标识来new不同的对象,然后再获取该对象的出行方式,就像下面这样

public String getPeopleTravelType(boolean isRich) {
    if (isRich) {
        return new RichPeople().travelType();
    }
    return new PoolPeople().travelType();
}

但是既然聊到了静态工厂方法,是不是有一种更优雅的写法呢?其实我们可以在抽象类里面创建一个静态工厂方法

public abstract class People {

    public abstract String travelType();

    public static People getInstance(boolean isRich) {
        if (isRich) {
            return new RichPeople();
        }
        return new PoolPeople();
    }
}

在人的抽象类写了个静态方法,方法返回类型为People,但实际返回类型是它的派生类,这时我们在getPeopleTravelType()方法就可以变成下面这样子了:

    public String getPeopleTravelType(boolean isRich) {
        People people = People.getInstance(isRich);
        return people.travelType();
    }

这样做的好处是People类作为一个Api时,这种隐藏实现类的方式会让Api变得十分整洁,因此非常适合基于接口的框架。

此外,Java8以后接口也可以有默认方法和静态方法了,接口也可以使用上述方式。这样我们在使用这个接口时,就不用去关注其具体实现类了,是不是会变得简洁不少呢。

4、静态工厂的第四大优势在于,所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值

其实这个优点在上面已经介绍过了,3、4一般可以都会结合在一起使用,这里就不赘述了

5、静态工厂的第五大优势在于,方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在

这种灵活的静态工厂方法构成了服务提供者框架。服务提供者框架是指这样一个系统:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来。 服务提供者框架有三个重要的组件:

  • 服务接口,这是提供者实现的;
  • 提供者注册Api,这是提供者用来注册实现的;
  • 服务访问Api,这是客户端用来获取服务的实例。

下面通过一个简单的例子来介绍下,假定在日常生活中,国家制定了标准生产一种食品需要先向国家注册,才能投入生产,下面就是国家给我们提供的标准:

/**
 * 食品接口
 */
public interface Food {
    
}

/**
 * 食品管理类
 */
public class FoodManager {

    private static final Map<String, Food> FOOD_MAP = new HashMap<>();

    /**
     * 注册食品
     * @param type 食品类型
     * @param food 食品
     */
    public static void registerFood(String type, Food food) {
        FOOD_MAP.put(type, food);
    }

    /**
     * 根据食品类型获取食品
     * @param type 食品类型
     * @return
     */
    public static Food getFood(String type) {
        return FOOD_MAP.get(type);
    }

}

可以看到有一个食品接口和一个食品管理类,我们想生产一种食品时,需要先向国家注册,然后才能进行生产。

假定我们现在要注册并生产可乐,就需要先创建一个可乐类并实现食品接口,然后调用注册方法提供可乐类型饮料和可乐这个类完成注册,之后就能通过getFood方法完成生产了,也就是:

/**
 * 可乐类
 */
public class Coke implements Food{
    
}

//注册和生产流程
public void getFood() {
    String type = "yinliao";
    FoodManager.registerFood(type, new Coke());
    Food food = FoodManager.getFood(type);
    System.out.println("生产了食品:" + food.getClass());
}

这样再回过头看下上面所讲的服务提供者框架,服务接口就是Food接口,提供者注册Api就是register方法,服务访问Api就是getFood方法。

上面的简单例子可能并不能完全将提供者框架表述清楚,其实还有一个更好的例子,那就是JDBC API,对于JDBC来说,Connection就是服务接口的一部分,DriverManager.registerDriver是提供者注册API,DriverManager.gerConnection是服务访问Api,Driver是服务提供者接口。具体的我也不再赘述,大家可以看下JDBC这一块加深一下理解。

总结

总之,静态工厂方法是十分常用的,但是也不能完全替代构造器。静态工厂方法和公有构造器都各有用处,我们要理解它们各自的长处。
以上部分内容源于Effective Java第三版,如有侵权请联系删除。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值