《Effective Java》--01.考虑用静态工厂方法代替构造器

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

对于一个类,想要获得它自身的一个实例,最常用的方法就是提供一个公有的构造器。还有一种方法,也应该在每个程序员的工具箱中占有一席之地。那就是静态工厂方法,那么首先需要了解的就是什么是静态工厂方法?

《Java面向对象编程》对静态工厂方法的详解


创建类的实例的最常见的方式是用new语句调用类的构造方法。在这种情况下,程序可以创建类的任意多个实例,每执行一条new语句,都会导致Java虚拟机的堆区中产生一个新的对象。假如类需要进一步封装创建自身实例的细节,并且控制自身实例的数目,那么可以提供静态工厂方法。

例如Class实例是Java虚拟机在加载一个类时自动创建的,程序无法用new语句创建java.lang.Class类的实例,因为Class类没有提供public类型的构造方法。为了使程序能获得代表某个类的Class实例,在Class类中提供了静态工厂方法forName(String name),它的使用方式如下:

Class c=Class.forName("Sample"); //返回代表Sample类的实例

静态工厂方法与用new语句调用的构造方法相比,有以下区别:

(1)构造方法的名字必须与类名相同。这一特性的优点是符合Java语言的规范,缺点是类的所有重载的构造方法的名字都相同,不能从名字上区分每个重载方法,容易引起混淆。

静态工厂方法的方法名可以是任意的,这一特性的优点是可以提高程序代码的可读性,在方法名中能体现与实例有关的信息。例如例程11-5的Gender类有两个静态工厂方法:getFemale()和getMale()。

例程11-5 Gender.java
    public class Gender{

    private String description;

    private static final Gender female=new Gender("女");

    private static final Gender male=new Gender("男");

    private Gender(String description){
        this.description=description;
    }


    public static Gender getFemale(){
         return female;
     }


     public static Gender getMale(){
        return male;
     }

    public String getDescription(){
        return description;
    }
}

这一特性的缺点是与其他的静态方法没有明显的区别,使用户难以识别类中到底哪些静态方法专门负责返回类的实例。为了减少这一缺点带来的负面影响,可以在为静态工厂方法命名时尽量遵守约定俗成的规范,当然这不是必需的。目前比较流行的规范是把静态工厂方法命名为valueOf或者getInstance。

  1. valueOf:该方法返回的实例与它的参数具有同样的值,例如:
Integer a=Integer.valueOf(100); //返回取值为100Integer对象

从上面代码可以看出,valueOf()方法能执行类型转换操作,在本例中,把int类型的基本数据转换为Integer对象。

2.getInstance:返回的实例与参数匹配,例如:

//返回符合中国标准的日历
Calendar cal=Calendar.getInstance(Locale.CHINA);

(2)每次执行new语句时,都会创建一个新的对象。而静态工厂方法每次被调用的时候,是否会创建一个新的对象完全取决于方法的实现。

(3)new语句只能创建当前类的实例,而静态工厂方法可以返回当前类的子类的实例,这一特性可以在创建松耦合的系统接口时发挥作用,参见本章11.3.5节(松耦合的系统接口)。
静态工厂方法最主要的特点是:每次被调用的时候,不一定要创建一个新的对象。利用这一特点,静态工厂方法可用来创建以下类的实例:

l 单例类:只有惟一的实例的类。

l 枚举类:实例的数量有限的类。

l 具有实例缓存的类:能把已经创建的实例暂且存放在缓存中的类。

l 具有实例缓存的不可变类:不可变类的实例一旦创建,其属性值就不会被改变。

在下面几节,将结合具体的例子,介绍静态工厂方法的用途。

更过的内容,请自行查阅相关书籍,这里不再截取。

静态工厂方法的好处

1.静态工厂方法有名称。

如果构造器参数本身没有确切的描述正被返回的对象,那么具有适当名称的静态工厂会更容易使用,产生的客户端代码也更易于阅读。例如,构造器BigInteger(int,int,Random)返回的BigInteger可能为素数,如果用名为BigInteger.probablePrime的静态工厂方法来表示,显然更为清楚。这样有助于提高程序的可阅读性。

当一个类需要多个带有相同签名的构造器时,就用静态方法代替构造器,并且慎重地选择名称以便突出它们之间的区别。

注:方法签名由方法名称和一个参数列表(方法的参数的顺序和类型)组成。不包括方法的返回类型,不包括返回值和访问修饰符。

2.不必在每次调用的时候都创建一个新对象。
静态工厂方法可以使不可变类使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免创建不必要的重复对象。

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

编写实例受控类的好处:
a)实例受控使得类可以确保它是一个Singleton(单例)或者不可实例化的
b)它使得不可变类可以确保不会存在两个相等的实例,即当且仅当a==b的时候才有a。equals(b)为true。这样可以用==代替equals,提高性能。枚举(enum)类型便保证了这一点。

3.静态工厂方法可以返回原返回类型的任何子类型的对象。
这种灵活性的一种应用是,API可以返回对象,同时又不会使对象的类变成共有的。以这种方式隐藏实现类会是API变得非常简洁。这项技术适用于基于接口的框架(interface-based framework),因为在这种框架中,接口为静态工厂方法提供了自然返回类型。接口不能有静态方法,因此按照惯例,接口Type的静态工厂方法被放在一个名为Types的不可实例化的类中。

公有的静态工厂方法返回的对象的类不仅可以是非公有的,而且该类还可以对着每次调用发生变化,这取决于静态工厂方法的参数值。只要是已声明的返回类型的子类型,都是允许的。为了提高软件的可维护性和性能,返回对象的类也可能随着发行版本的不同而不同。

静态工厂方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不必存在。这种灵活的静态工厂方法构成了服务提供者框架(Service Provider Framework)的基础。

服务提供者框架的组件:
a)服务接口(Service Interface) 重要
b)提供者注册API(Provider Registration API) 重要
c)服务访问API 重要
d)服务提供者接口 可选

4.在创建参数化实例的时候,它们使代码变得更加简洁。
例如:

Map<String,List<String>> m = new HashMap<String,List<String>>();

随着类型参数变得越来越长,越来越复杂,这一冗长的说明令人痛苦。但是使用静态工厂方法,编译器可以替你找到类型参数。这杯称作类型推导(type inference)。
假设HashMap提供了这个静态工厂:

public static <k,v> HashMap<k,v> new Instance(){
    return new HashMap<k,v>();
}

那么可以使用以下代码获取实例

Map<String,List<String>> m = HashMap.newInstance();

静态工厂方法的缺点

1.类如果不含公有的或者受保护的构造器,就不能被子类化。

2.它们与其他静态方法实际上没有任何区别。

对于提供了静态工厂方法而不是构造器的类来说,想要查明如何实例化一个类,很困难。

静态工厂方法可以遵守标准的命名习惯,弥补这一劣势。下面是静态工厂方法一些惯用名称:

value of –不太严格地讲,该方法返回的实例与它的参数具有相同的值。这样的静态工厂方法实际上是类型转换方法。

of –value of的一种更为简洁的替代,在EnumSet中使用并流行起来。

getInstance –返回的实例是通过方法的参数来描述的,但是不能够说与参数具有同样的值。对于Singleton来说,该方法没有参数,并返回唯一的实例。

newInstance–像getInstance一样,但newInstance能够确保返回的每个实例都与所有其他实例不同。

getType –像getInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的对象类型。

newType-像newInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的对象类型。

简而言之,静态工厂方法和共有的构造器都各有用处,我们需要理解它们各自的长处。静态工厂通常更加适合,因此切忌第一反应就是提供共有的构造器,而不考虑静态工厂。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值