java静态工厂方法详细解析——使用静态工厂方法代替构造器

一、什么是静态工厂方法?

对于类而言,在我们需要获取一个实例时,最传统的方法都是通过new新建一个对象,这是jvm通过调用构造函数帮我们实例化出来的对象。而静态工厂方法则是另外一种不通过new来获取一个实例的方法,我们可以通过一个类中公有的静态方法,来返回一个实例。
比如有这样一个People类:

class People{
	String name;
	int age;
	int weight;
}

我们传统的获取实例都是通过new:

People People=new People();

而静态工厂方法可以在类中添加一个公有静态方法来返回一个实例,还是以上面的People类为例子:

class People{
	String name;
	int age;
	int weight;

	public static People getPeople(){
		return new People();
	}
}

这样我们就可以通过getPeople这个静态方法来获取一个实例:

People firstPeople=People.getPeople();

虽然通过构造器获取实例是最传统的方法,但是在实际开发中,用静态工厂方法代替构造器也是比较常用的。

二、静态工厂方法的优势

总结的说,静态工厂方法有五大优势:

1、静态工厂方法有名称
对于构造器来说,我们都知道,构造函数的命名只能是类名,而构造函数重载也只能通过参数的不同去区分调用不同的构造函数,一个类只能有一个带有指定参数的构造器,而若是两个构造器需要的参数是相同类型的话,开发人员通常会通过改变这两个参数的顺序来定义两个构造器,注意,这种情况只能适合多参构造函数,在单参构造器中则无法实现了。而静态工厂方法便显得简单许多,因为它能被我们定义成不同的名字,这使用起来会更加的方便,在某种程度上来说代码也会更易阅读。
还是以之前的Peple类做例子,当我们获取实例时想要对name和age初始化,需要定义这样的构造函数:

class People{
	String name;
	int age;
	int weight;
	public People(String name,int age){
		this.name=name;
		this.age=age;
	}
}	

要是我们同时也需要对name和weight进行初始化,则需要改变参数中String和int类型的位置,以实现不同的构造器:

public People(int weight,String name){
		this.name=name;
		this.age=age;
	}

而这样有时候则会让我们不知道该调用哪个构造函数,又或者需要在两个不同的实例中初始化age和weight的值的时候,是没办法同时出现People(int age)和People(int weight)这样的两个构造函数的,因此我们需要通过静态工厂方法来实现:

class People{
	String name;
	int age;
	int weight;
	public static People getNameWeightPeople(String name,int age){
		People nwp=new People();
		nwp.name=name;
		nwp.weight=weight;
		return nwp;
	}
	public static People getNameAgePeople(String name,int age){
		People nap=new People();
		nap.name=name;
		nap.age=age;
		return nap;
	}
	public static People getAgePeople(int age){
		People ap=new People();
		ap.age=age;
		return ap;
	}
	public static People getWeightPeople(int weight){
		People wp=new People();
		wp.weight=weight;
		return wp;
	}
}

这样我们便能通过静态工厂方法定义不同初始化属性但参数类型相同的方法来获取不同的实例对象了。

2、静态工厂方法不用在每次调用的时候都创建一个新的对象
“如果程序经常请求创建相同的对象,并且创建对象的代价很高,则静态工厂方法能极大地提升程序的性能。”这是《Effective Java》这本书中对这一优势的说法。
如果我们的代码在调用某个类的时候只需要一个实例,但是并不关心这个实例是否是一个新的对象,此时通过静态工厂方法便可以很方便的实现,提升程序性能。
例如加载数据库驱动的时候:

Class.forName("com.mysql.jdbc.Driver");

我们通过这样的一句代码便能加载驱动,而我们根本不关心这个方法返回的实例,因此便可以不创建实例对象。

3、静态工厂方法可以返回原返回类型的任何子类型对象
这样子看起来貌似有点绕,简单的来说,在构造器中我们只能返回当前构造器所在类的对象,而通过静态工厂方法我们可以任意选择返回类型,因此便可以返回该类的任何子类型。
还是以People类为例子:

class People{
	String name;
	int age;
	int weight;
	public static People getWomen(){
		return new Women();
	}	
}
class Women extends People{
}

4、静态工厂方法所返回的对象可以随着每次调用而发生变化,这取决于参数值
也就是说,我们可以通过参数值的不同,来选择返回哪个实例(注意,这里说的是参数值而不是参数类型),而这个实例的类型只要是已经声明好的返回类型的子类型,就都是被允许的。
例如:

class People{
	String name;
	int age;
	int weight;
	public static People getOne(int age){
		if(age>=18){
			return new Adult();
		}else {
			return new Child();
		}
	}
	
}
class Child extends People{

}
class Adult extends People{

}

在这个例子中,Child和Adult这两个类的存在对于客户端来说是不可见的,客户端永远不知道也不关心他们从这个静态工厂方法中得到的对象是一个什么类型,它们只关心这个对象是People的某个子类。

5、静态工厂方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在
这个优势构成了服务提供者框架的基础,服务提供者框架是指多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来。
以JDBC的API为例,对于JDBC来说,Connection就是服务接口的一部分,DriverManager.registerDriver是提供者注册API,DriverManager.getConnection是服务访问API,Driver是服务提供者接口。
我们要通过jdbc获取一个mysql连接,是这样获取的:

Class.forName("com.mysql.jdbc.Driver");   
Connection connection=DriverManager.getConnection("jdbc:mysql://localhost:3306/database","root","123456");  

这里我们获取的连接对象是Connection,按住Ctrl点进去我们可以发现Connection是一个接口,而接口又如何能操作数据库,所以我们这里得到的其实是Connection接口的实现类,而这个实现类是对我们不可见的,因为我们根本不需要去关心这个实现类是什么,我们只需要用这个Connection接口便可以实现对数据库的操作。而我们获取到的这个实现类,是通过DriverManager.getConnection这个方法得到的,我们通过查看这个方法的源码可以知道这个方法其实是一个静态工厂方法,而很明显Connection的实现类并不存在于包含这个静态工厂方法的DriverManager类中。

三、静态工厂方法的缺点

1、类如果不含公有的或者受保护的构造器,就不能被子类化
这个很好理解,如果类不含上述的这两种构造器,当然就没办法被继承。但实际这样或许也是一个好处,因为这样能鼓励程序员使用复合,而不是继承,这正是不可变类型所需要的。

2、程序员很难发现静态工厂方法
因为静态工厂方法是自定义的,它们没有像构造器那样被明确规定如何实例化,因此程序员往往很难查明如何通过静态工厂方法实例化一个类,因此我们便需要遵守一些静态工厂方法的惯用名称。
以下列出一小部分:

①from——类型转换方法,只有单个参数。返回该类型的一个相对应实例,例如:

Date d=Date.from(instant)

②of——聚合方法,有多个参数,返回该类型的一个实例,把它们合并起来,例如:

Set<Rank> facecards=Enumset.of(JACK, QUEEN, KING);

③valueOf——比from和of更烦琐的一种替代方法,例如:

BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);

④ instance或者getInstance——返回的实例是通过方法的(如有)参数来描述的,但是不能说与参数具有同样的值,例如:

Stackwalker luke -Stackwalker.getInstance(options);

⑤create或者 newInstance——像 instance或者 getInstance一样,但 create或者 newInstance能够确保每次调用都返回一个新的实例,例如:

Object newArray=Array.newInstance(classObject,arrayLen);

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

FileStore fs=Files.getFileStore(path);

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

BufferedReader br=Files.newBufferedReader(path);

⑧type——getType和 newType的简版,例如:

List<Complaint> litany=Collections.list(legacyLitany);

四、总结

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

本文参考——《Effective Java》

发布了2 篇原创文章 · 获赞 3 · 访问量 133

猜你喜欢

转载自blog.csdn.net/a517865058/article/details/104829478
今日推荐