1、用静态工厂方法代替构造器
Turtle_Zhang 人气:1一、客户端获取类的一个实例,有两种解决方案
- 最传统的方法就是提供一个公有的构造器。
- 类提供一个公有的静态工厂方法,返回一个类的实例的静态方法。
二、静态工厂方法的优点
1、有方法名,可读性强
- 不同的静态工厂方法,有自己的专属名称。如果构造器的参数本身没有确切地描述正被返回的对象,那么具有名称的静态工厂方法会更容易使用,产生的客户端代码更易于阅读。
- 如果一昧的创建各种不同方法签名的构造方法,就会导致构造方法爆炸,而且不利于管理,用户仅仅依靠参数类型、个数来分辨不同构造方法的作用,直接增加了开发成本,还增加了出错的几率,代码可读性不强。
- 静态工厂方法有名称,当一个类需要多个带有不同签名的构造器时,我们可以使用静态工厂方法代替构造器,并且用规则的命名来展示出不同方法对应的作用。只要方法命名规范,用户完全可以通过IDE来补全所需要的方法即可,而不需要从头到尾的看源码,或者是仔细的阅读文档。
2、案例:
很简单,拿生成订单来说。如果生成订单这个作为接口,那么会被多方机构调用。如:A机构所需参数为count、money。B机构所需参数为count、money、address。C机构仅仅是需要money。等等机构的不同需求参数。
a)、使用构造器:
1 package com.turtle.demo; 2 3 public class GenerateOrder { 4 5 private int count; 6 7 /** 8 * 备注:关于钱,切记不要使用double类型,这是例子,就暂时使用double了 9 */ 10 private double money; 11 12 private String address; 13 14 /** 15 * 需要一个参数的构造器 16 * @param money:金额 17 */ 18 public GenerateOrder(double money){ 19 this(0,money,null); 20 } 21 22 /** 23 * 需要两个参数的构造器 24 * @param count:数量 25 * @param money:金额 26 */ 27 public GenerateOrder(int count, double money){ 28 this(count,money,null); 29 } 30 31 /** 32 * 需要三个参数的构造器 33 * @param count:数量 34 * @param money:金额 35 * @param address:地址 36 */ 37 public GenerateOrder(int count, double money, String address) { 38 this.count = count; 39 this.money = money; 40 this.address = address; 41 } 42 }
b)、使用静态工厂方法(不是最终形态)
1 package com.turtle.demo; 2 3 public class GenerateOrder { 4 5 private int count; 6 7 /** 8 * 备注:关于钱,切记不要使用double类型,这是例子,就暂时使用double了 9 */ 10 private double money; 11 12 private String address; 13 14 /** 15 * 需要三个参数的私有化构造器 16 * @param count:数量 17 * @param money:金额 18 * @param address:地址 19 */ 20 private GenerateOrder(int count, double money, String address) { 21 this.count = count; 22 this.money = money; 23 this.address = address; 24 } 25 26 /** 27 * A机构所需参数为count、money。 28 * @param count 29 * @param money 30 * @return 31 */ 32 public static GenerateOrder generateOrderByA(int count,double money){ 33 return new GenerateOrder(count,money,null); 34 } 35 36 /** 37 * B机构所需参数为count、money、address。 38 * @param count 39 * @param money 40 * @param address 41 * @return 42 */ 43 public static GenerateOrder generateOrderByB(int count,double money,String address){ 44 return new GenerateOrder(count,money,address); 45 } 46 47 /** 48 * C机构仅仅是需要money 49 * @param money 50 * @return 51 */ 52 public static GenerateOrder generateOrderByC(double money){ 53 return new GenerateOrder(0,money,null); 54 } 55 }
c)、调用:
1 package com.turtle.test; 2 3 import com.turtle.demo.GenerateOrder; 4 5 /** 6 * 对生成订单做测试 7 */ 8 public class TestGenerateOrder { 9 10 public static void main(String[] args) { 11 12 // 使用构造器 13 // A 机构创建订单 14 GenerateOrder generateOrder_A = new GenerateOrder(1,200D); 15 16 // B 机构创建订单 17 GenerateOrder generateOrder_B = new GenerateOrder(1,200D,"深圳"); 18 19 // C 机构创建订单 20 GenerateOrder generateOrder_C = new GenerateOrder(200D); 21 22 23 // 使用静态工厂方法 24 // A 机构创建订单 25 GenerateOrder generateOrder_AA = GenerateOrder.generateOrderByA(1,200D); 26 27 // B 机构创建订单 28 GenerateOrder generateOrder_BB = GenerateOrder.generateOrderByB(1,200D,"深圳"); 29 30 // C 机构创建订单 31 GenerateOrder generateOrder_CC = GenerateOrder.generateOrderByC(200D); 32 } 33 }
2、不必在每次调用它们的时候都创建一个新对象。
可以使不可变类直接使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,如果程序经常请求创建相同的对象,并且创建对象的代价很高,则这项技术可以极大地提升性能。
静态工厂方法能够为重复的调用返回相同对象,这样有助于类总能严格控制在某个时刻哪些实例应该存在,可以直接和单例搭配起来使用。
单例的几种写法:
1 package com.turtle.singleton; 2 3 public class SingletonDemo { 4 5 /** 6 * 构造函数私有化 7 */ 8 private SingletonDemo(){ 9 } 10 11 // 维护一个单例对象 12 private static SingletonDemo singletonDemo = new SingletonDemo(); 13 14 public static SingletonDemo getInstance(){ 15 return singletonDemo; 16 } 17 }
1 package com.turtle.singleton; 2 3 public class SingletonDemo { 4 5 /** 6 * 构造函数私有化 7 */ 8 private SingletonDemo(){ 9 } 10 11 // 维护一个单例对象 12 private static SingletonDemo singletonDemo; 13 14 public static SingletonDemo getInstance(){ 15 if(singletonDemo == null){ 16 singletonDemo = new SingletonDemo(); 17 } 18 return singletonDemo; 19 } 20 }
1 package com.turtle.singleton; 2 3 public class SingletonDemo { 4 5 /** 6 * 构造函数私有化 7 */ 8 private SingletonDemo(){ 9 } 10 11 // 维护一个单例对象 12 // volatile 是1.5后优化JAVA内存模型的关键字 13 private volatile static SingletonDemo singletonDemo; 14 15 public static SingletonDemo getInstance(){ 16 if(singletonDemo == null){ 17 // 由于内存模型,静态工厂方法多线程情况下也会有问题,即使用了双重锁定也一样 18 synchronized (SingletonDemo.class){ 19 if(singletonDemo == null){ 20 singletonDemo = new SingletonDemo(); 21 } 22 } 23 } 24 return singletonDemo; 25 } 26 }
3、它们可以返回原返回类型的任何子类型的对象
构造器只能返回当前类的实例,无法返回子类的实例。虽说推荐的是复合,而不推荐继承。但是有时还是会因为业务需求,可能需要返回对应的子类型对象。这个时候依靠构造器就无法完成了,只能单独创建对应的返回子对象的方法来实现对应需求。但是使用静态方法我们就可以很好的解决这个问题。还减少了重复的方法创建。
package com.turtle.demo; public class Animal { protected Animal(){ } private static Animal dog; private static Animal snake; /** * 返回子类型 Dog * @return */ public static Animal getAnimal_Dog(){ if(dog == null){ dog = new Dog(); } return dog; } /** * 返回子类型 Snake * @return */ public static Animal getAnimal_Snake(){ if(snake == null){ snake = new Snake(); } return snake; } }
4、返回的类可以随着每次调用而动态变化,这取决于静态工厂的方法的参数值
静态工厂的第四大优势在于,所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值。只要是已声明的返回类型的子类型,都是允许的。返回对象的类也可能随着发行版本的不同而不同。
EnumSet 没有公有的构造器,只有静态工厂方法。在OpenJdk实现中,它们返回两种子类之一的一个实例,具体则取决于底层枚举类型的大小:如果它的元素有6 4个或者更少,就像大多数枚举类型一样,静态工厂方法就会返回一个RegularEnumSet实例,用单个long进行支持;
如果枚举类型有65个或者更多元素,工厂就返回JumboEnumSet实例,用一个long数组进行支持。
1 public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) { 2 Enum<?>[] universe = getUniverse(elementType); 3 if (universe == null) 4 throw new ClassCastException(elementType + " not an enum"); 5 6 if (universe.length <= 64) 7 return new RegularEnumSet<>(elementType, universe); 8 else 9 return new JumboEnumSet<>(elementType, universe); 10 }
5、代码更加简洁,减少我们的代码量。
1 package com.turtle.demo; 2 3 class MyMap<K,V> { 4 5 public MyMap() { 6 } 7 8 public static <K,V> MyMap<K,V> getInstance(){ 9 return new MyMap<K, V>(); 10 } 11 }
1 package com.turtle.demo; 2 3 public class Main { 4 public static void main(String[] args) { 5 // 实例时需要制定泛型 6 MyMap<String, String> map1 = new MyMap<String, String>(); 7 8 //更加简洁,不需要重复指明类型参数,可以自行推导出来 9 MyMap<String, String> map2 = MyMap.getInstance(); 10 } 11 }
6、静态工厂返回的类可以不存在
静态工厂的第五大优势在于,方法返回对象所属的类,在编写包含该静态工厂方法类时可以不存在。
(静态工厂方法最典型的实现--服务提供者框架 )服务提供者框架包含四大组件:
- 服务接口:这是服务提供者要去实现的接口
- 服务提供者接口:生成服务接口实例的工厂对象(就是用来生成服务接口的)(可选)
- 提供者注册API:服务者 提供服务者自身的实现
- 服务访问API:根据客户端指定的某种条件去实现对应的服务提供者
1 //四大组成之一:服务接口 2 public interface LoginService {//这是一个登录服务 3 public void login(); 4 } 5 6 //四大组成之二:服务提供者接口 7 public interface Provider {//登录服务的提供者。通俗点说就是:通过这个newLoginService()可以获得一个服务。 8 public LoginService newLoginService(); 9 } 10 11 /** 12 * 这是一个服务管理器,里面包含了四大组成中的三和四 13 * 解释:通过注册将 服务提供者 加入map,然后通过一个静态工厂方法 getService(String name) 返回不同的服务。 14 */ 15 public class ServiceManager { 16 private static final Map<String, Provider> providers = new HashMap<String, Provider>();//map,保存了注册的服务 17 18 private ServiceManager() { 19 } 20 21 //四大组成之三:提供者注册API (其实很简单,就是注册一下服务提供者) 22 public static void registerProvider(String name, Provider provider) { 23 providers.put(name, provider); 24 } 25 26 //四大组成之四:服务访问API (客户端只需要传递一个name参数,系统会去匹配服务提供者,然后提供服务) (静态工厂方法) 27 public static LoginService getService(String name) { 28 Provider provider = providers.get(name); 29 if (provider == null) { 30 throw new IllegalArgumentException("No provider registered with name=" + name); 31 32 } 33 return provider.newLoginService(); 34 } 35 }
参考这篇文章进一步理解:JAVA 服务提供者框架介绍
三、静态工厂方法的缺点
1、静态工厂方法依赖于构造函数的创建
上面提到了一些静态工厂方法的优点,那么任何事情都有利弊,静态工厂方法主要缺点在于,类如果不含公有的或者受保护的构造器,就不能被子类化。例如,要想将Collections Framework中任何便利的实现类子类化,这是不可能的。
静态工厂方法最终也是调用该类的构造方法,如果没有该类的构造方法,静态工厂的方法也就没有意义,也就是说,静态工厂方法其实是对构造方法的一层封装,最终还是调用的类的构造方法。
2、静态工厂方法很难被发现
1、其实主要还是由于习惯,创建对象就直接new就好了,没有去考虑其他的方法会不会更有优势。
2、在API文档中,它们没有像构造器那样在API文档中被标明,因此,对于提供了静态工厂方法而不是构造器的类来说,要想查明如何实例化一个类是非常困难的。下面提供了一些静态工厂方法的惯用名称。这里只列出来了其中的一小部分
from ——— 类型转换方法,它只有单个参数,返回该类型的一个相对应的实例,例如: Date d = Date.form(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 ——— 像newInstanfe 一样,但是在工厂方法处于不用的类中的时候使用,Type表示工厂方法返回的对象类型,例如: BufferedReader br = Files.newBufferedReader(path); type ——— getType 和 newType 的简版,例如: List<Complaint> litany = Collections.list(legacyLitancy);
四、总结
简而言之,静态工厂方法和公有构造器都各有用处,我们需要理解它们各自的长处。静态工厂经常更加合适,因此切忌第一反应就是提供公有的构造器,而不先考虑静态工厂。
五、说明
这部分东西比较灵活,加上自己能力有限。有些地方可能会存在理解偏差,这里希望大佬们可以抽出你们宝贵的时间对文章中的错误点指正出来,我会及时做调整。谢谢!
【内容源自《Effective Java》(中文原书第3版) + 自己的理解 + 大佬博客参考】
加载全部内容