[Effective Java]考虑用静态工厂方法代替构造器

本文主要介绍如何使用静态工厂方法已经在那种场合来使用这种方式代替构造方法。       

众所周知,对于类而言,我们为了获得一个类的实例对象,通常情况下会提供一个公有的(public) 的构造器。当然除了这种方法以外,我们还可以通过给类提供一个public的静态工厂方法(static factory method)的方式来完成,让它返回一个类的实例。

先看一个简单的Boolean的示例,这个示例将boolean基本类型值转换成一个Boolean对象的引用。

public static Boolean valueOf(boolean b){
       return b ? Boolean.TRUE : Boolean.FALSE;
}
需要注意的是这里的静态工厂方法与设计模式中所说的工厂方法模式不同。


那么,静态工厂方法相比于公有的构造器有哪些优势呢?

1. 静态工厂方法可以有不同的名称,代码更清楚,更易读。

简单解释一下:

      场景:构造器的命名都一致,一个类只能有一个指定签名的构造器。

      当一个类需要提供多个构造器时,通常只是通过不同的形参类型的顺序加以区分,但其函数名还是相同的,无法提供较高的区分度。

  结论:当一个类需要多个带有相同签名的构造器时,不妨考虑用静态工厂方法代替构造器,并且慎重地选择名称以便突出它们之间的区别。这样该静态工厂方法有名称,通过赋予有意义的名称,使用该方法的程序员可以清晰的知道该方法的含义。

     方法的签名(signature)由它的名称和所有参数的返回类型组成,而不包括它的返回类型。

2. 静态工厂方法不必在每次调用它们的时候都创建一个新对象。

比如你可以在静态工厂方法里限定创建该对象的个数,当超出规定的个数时,返回缓存里的对象。

      场景:调用构造器时每次都创建新对象。

      这一点很显然,我们知道Singleton其实也提供一个静态工厂方法获取实例。

   除此之外,可以用==代替equals()方法,达到性能的提升。

3. 静态工厂方法可以返回原返回类型的任何子类型的对象。

      场景:构造器只能返回当前类的实例,无法返回子类的实例。

      这个优点的确很有用,能够充分利用多态,使代码更具有可扩展性。

      设计模式中的工厂方法也有体现,可根据入参type返回类型为Types的不同子类实例。

public Types getType(String type) {...}
这样我们在选择返回对象的类时就有更大的灵活性,这种灵活性的应用就是API可以返回对象,同时又不会使对象的类变成公有的。以这种方式隐藏实现类会使API变得简洁。这项技术适合于基于接口的框架(interface-based framework)。这种面向接口的编程也提高了软件的可维护性和性能。

4. 静态工厂方法在创建参数化类型实例的时候使代码变得更加简洁。

      场景: Java的泛型类在实例化时,还是需要写两次类型参数,非常冗长

      静态工厂方法可以帮你改善这种情况:

      举个例子,假设在HashMap类中提供如下静态工厂方法:

public static <k, v> HashMap<k, v> newInstance(){
   return new HashMap<k, v>();  
}
那么,调用时应该是这样的:

HashMap<Stirng, List<String>> m = HashMap.newInstance();
而在调用参数化的构造器时,则通常需要这样做,显得比较繁琐。

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

另外值得一提的是,静态工厂方法返回的对象所属的类,在编写包含该静态工厂方法的类时不必存在。这种灵活的静态工厂方法构成了服务提供者框架(Service Provider Framework)的基础,例如JDBC的API。

所谓服务提供者框架是指这样的一个系统:多个服务提供者实现同一个服务,由框架(即系统)来负责为客户端提供这种服务的多个实现并将这些实现解耦。

服务提供者框架有三个主要的组件:1.服务接口(Service interface),这是提供者实现的。2.提供者注册API(Provider Registration API),这是系统用来注册实现,让客户端访问它们的。3.服务访问API(Service Access API),这是客户端获取服务实例用的。

另外还有第四个可选接口,服务提供者接口(Service Provider Interface),负责创建其服务实现的实例。若没有服务提供者接口,实现就按照类名称进行注册,并通过反射方式进行实例化。对JDBC而言,Connection就是它的服务接口,DriverManager.registerDriver就是它的提供者注册API,DriverManager.getConnection是服务访问API,Driver就是服务提供者接口。

下面是一个简单的服务提供者框架的实现:

// Service provider framework sketch - Service interface
package com.xjtu.cruise.chapter02.item01;
public interface Service {
// Service-specific methods go here
}

// Service provider framework sketch 
//Noninstantiable class for service registration and access
package com.xjtu.cruise.chapter02.item01;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class Services {
	private Services() {
	} // Prevents instantiation (Item 4)

	// Maps service names to services
	private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();
	public static final String DEFAULT_PROVIDER_NAME = "<def>";

	// Provider registration API
	public static void registerDefaultProvider(Provider p) {
		registerProvider(DEFAULT_PROVIDER_NAME, p);
	}

	public static void registerProvider(String name, Provider p) {
		providers.put(name, p);
	}

	// Service access API
	public static Service newInstance() {
		return newInstance(DEFAULT_PROVIDER_NAME);
	}

	public static Service newInstance(String name) {
		Provider p = providers.get(name);
		if (p == null)
			throw new IllegalArgumentException(
					"No provider registered with name: " + name);
		return p.newService();
	}
}

// Service provider framework sketch - Service provider interface 
package com.xjtu.cruise.chapter02.item01;

public interface Provider {
	Service newService();
}

// Simple test program for service provider framework

package com.xjtu.cruise.chapter02.item01;

public class Test {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		Services.registerDefaultProvider(DEFAULT_PROVIDER);
		Services.registerProvider("comp", COMP_PROVIDER);
		Services.registerProvider("armed", ARMED_PROVIDER);

		Service s1 = Services.newInstance();
		Service s2 = Services.newInstance("comp");
		Service s3 = Services.newInstance("armed");
		System.out.printf("%s %s %s\n", s1, s2, s3);
	}	
	
	private static Provider DEFAULT_PROVIDER = new Provider() {
		@Override
		public Service newService() {
			return new Service() {
				@Override
				public String toString() {
					return "Default service";
				}
			};
		}
	};
	
	
	private static Provider COMP_PROVIDER = new Provider() {
		@Override
		public Service newService() {
			return new Service() {
				@Override
				public String toString() {
					return "Complementary service";
				}
			};
		}
	};
	
	
	private static Provider ARMED_PROVIDER = new Provider() {
		@Override
		public Service newService() {
			return new Service() {
				@Override
				public String toString() {
					return "Armed service";
				}
			};
		}
	};

}


但是,我们也应该很清楚的认识到使用静态工厂方法带来的隐患

1. 如果类中没有提供public或protected的构造器,将造成该类不能子类化

  不能子类化在某些情况下是无法接受的,但是,这样也有好处:策略模式教导我们应该多用组合而不是继承,当一个类不能子类化后,组合将是你唯一的选择。

  

2. 静态工厂方法和其他静态方法本质上并没有区别

  前已经述及,静态工厂方法本质上就是一个静态方法。这使得使用人员在查阅JavaDoc时,可能将其和一般的静态方法混淆,造成使用上的困扰。

 

总结:

  鉴于构造器有各种各样的不便,可以考虑用静态方法代替。它会给我们带来以下优缺点:

   优点:

    1) 有名称;

    2)不必在每一次创建时都提供新对象;

    3) 返回子类型;

    4) 使代码更加简单。

   缺点:

    1)如果类中没有提供public或protected的构造器,将造成该类不能子类化;

    2)静态工厂方法和其他静态方法本质上并没有区别。


[Effective Java]考虑用静态工厂方法代替构造器,古老的榕树,5-wow.com

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。