这里的
静态工厂方法
与设计模式里面的工厂模式
不一样,二者并不完全等价 ;
目录
新手的日常:
我们平时一般都是通过类的构造器,来向外提供类的实例 ;这样做,在某些情况下,并不是很优美,或者说直接使用构造器很操蛋;
但是我们作为新手,因为学java的时候,就是这么学的,可能并不能感觉我们这样的做,有什么不妥;希望通过下面的场景学习,你能知道我们平时写的代码到底是多么的孬;
没见过,大海的广阔;久而久之,你眼中只有溪流的宽度;
场景 一:
我们的类对外提供 多个构造器,来实例化 具有不同功能的实例;
假如我们直接使用构造器创建对象,可能会让阅读代码的人,不知所云,阅读代码的人,往往需要借助API
才能知道,我们这个构造器是实例出的对象具有什么功能;
蹩脚的代码:
// 构造一个随机生成的正 BigInteger, 这可能是素数, 具有指定的 bitLength。
BigInteger bigInteger = new BigInteger(1,2,new Random()) ;
假如没有我写的注释,谁能知道 new BigInteger(1,2,new Random())
到底返回一个什么!?因此,我们应该使用 静态工厂方法
来替换掉这样的代码;
优美的代码:
BigInteger bigInteger = BigInteger.probablePrime(3,new Random()) ;
显然上面的代码,很明了,我们完全可以根据 静态工厂方法
的 名字,知道它具体返回什么对象;
从上面的例子中,我们可以发现静态工厂方法
的 优势之一:就是静态工厂方法有自己的名字,我们可以根据它提供的功能来命名它,让使用者一目了然; 相比之下,构造器就有很大的局限性,它的名字只能跟随类来,类的名字并不能涵盖类提供的对象的所有功能;
静态工厂方法
的名字,在一个地方也是具有优势的,或者说是首选项;
不同的构造器实例化的对象,具有不同的功能,但是多个构造器之间,仅仅是参数列表的参数顺序不同,这样,通过参数列表,我们是分不清每一个构造器是实例化哪一个功能的对象的;
----------直接构造器代码:----------
/**
* e1_1 假定功能:
* 根据构造器的参数,将参数相加的和、相减的商,赋值给answer
*/
public class e1_1 {
public float answer ;
public e1_1(int a,float b ){
answer = a + b ;
}
public e1_1(float a,int b){
answer = a - b ;
}
}
--------------------- 调用类 ---------------------
import org.junit.Test;
/**
* 调用类
*/
public class test1_1 {
@Test
public void test(){
// 对应这样的代码,不去翻看API 谁能分清谁是加、谁是减法呢
float answer1 = new e1_1(3, 5f).answer;
float answer2 = new e1_1(3f, 5).answer;
}
}
我们使用 静态工厂方法
来替换上述的写法 ;
--------------------- 使用静态工厂方法替换掉构造器 ---------------------
public class e1_1 {
public float answer;
// 构造器私有化
private e1_1(int a, float b) {
answer = a + b;
}
private e1_1(float a, int b) {
answer = a - b;
}
// 对外只提供静态工厂方法
public static float getAdd(int a, float b){
return new e1_1(a,b).answer ;
}
public static float getDec(int a, float b){
return new e1_1(b,a).answer ;
}
}
--------------------- 调用类 ---------------------
public class test1_1 {
@Test
public void test(){
// 现在的调用,一眼就能看出,谁是加、谁是减法
float answer1 = e1_1.getAdd(3, 5);
float answer2 = e1_1.getDec(3, 5);
}
}
小结
:通过上述的代码,我们知道了 静态工厂方法
的一个优势 ,就是它具有名字,我们可以直接提供的功能来作为名字,对使用者很友好 ;
场景二:
当我们每次需要一个类的对象的时候,假如,我们需要的对象,并没有什么不同 ;我们可以考虑,提供一个 静态工厂方法
, 每次都返回我们已经创建好的对象,这样,就避免了调用构造器
创建创建重复对象的缺点;
示例代码:
--------------------代码来自Boolean类--------------------
// TRUE、FALSE 是我们事先准备的对象
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
// 每次需要相同对象的时候,直接返回已经创建好的对象
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
对于这样设计的类的对象,我们可以通过 ==
运算符来判断对象是否相等,而不用复写 equal
方法,这样做,可以提升一些性能 ;
小结
:相比于构造器
,静态工厂方法
可以做到,每次调用都返回相同的对象,避免 重复创建新的对象 ;
场景三
我们可以使用 静态工厂方法
,来 灵活的选择返回的对象的类型,可以是原返回类型的任意子类型,也就是返回的类型可以是构造器类的子类。
EnumSet
类就是这样实现的,它根据枚举元素的个数,来选择换回哪一个实现类 ;
----------------代码来自EnumSet类----------------------
public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {
Enum[] universe = getUniverse(elementType);
if (universe == null)
throw new ClassCastException(elementType + " not an enum");
// 判断枚举的元素的个数,决定使用哪一个实现类
if (universe.length <= 64)
return new RegularEnumSet<>(elementType, universe);
else
return new JumboEnumSet<>(elementType, universe);
}
这样做,是 很灵活的;假如后期,我们发现,当元素大于128的时候,使用另一个实现会更有效率,那么JDK就可以将新的实现类添加进去;
但是对于客户端来说,完全是隔离的,因为我们无法知道,也不必知道JDK到底使用了哪一个实现类,只要知道它返回一个 EnumSet
类型 即可 ;
—————————–案例 一 ———————:
在JDK
源代码中,基于接口的框架Collections
就是这样做的;
该框架有一个 public
的Collection
接口,里面定义了集合应该有的操作,也就是一个集合应该是什么样的;
集合有好多种,不可改变集合、同步集合、xxx集合;它们都是是 Collection
接口的具体实现;JDK
将它们实现为 非public
类(在源代码里面,实现类都是Collections
类的内部类,当然不是内部类,也行,没有什么影响)。
这样做的意义在于,将这些具体实现类隐藏
起来,可以减少 API
的数量,由于是 非public
类,因此 API
中不需要对其进行任何讲解,(这样做也导致了缺点一
点的产生)
框架中还有一个对外 暴露
的 不可实例化
的 Collections
类,里面提供大量的静态工厂方法,来对外提供具体的集合实现 ,但是这些方法的返回值类型,都是 Collection
接口 ,这就 达到了灵活选择返回对象类型的目的 ,同时静态工厂方法具有名字,也便于使用者知道返回的是哪一种集合 ;
—————————–案例 二 ———————:
服务提供者框架;其中子类型的实现类,甚至在定义静态工厂方法的时候,都不必存在 ;
场景四:
在使用泛型
的时候,静态工厂方法
,可以为我们创造一些便利;
先看下我们平时写的泛型,这样的代码,多孬哦,左右两边都需要写上泛型参数,泛型很长的时候,写的很烦人,虽然IDE会自动提示,但是这么长看着也恶心啊 ;
Map<String,List<ArrayList<Integer>>> map = new HashMap<String, List<ArrayList<Integer>>>() ;
上述的泛型是JDK定义的,我们无法去更改了;但是我们可以为自己的类,写上静态工厂方法,便于泛型的使用 ;
假如我们自己写 HashMap类,那么我们就应该提供一个静态工厂方法:
public static <K,V> HhaspMap<K,V> newInstance(){
return new HashMap<K,V>() ;
}
--------------------------------
// 这样我们再使用泛型,就会很简洁
Map<String,List<ArrayList<Integer>>> map = HashMap.newInstance();
优点
经过上述的场景分析,其实可以看出来,一个场景就是一个优点的展示;
- 当一个类有多个
重载
的构造器
的时候,具有名字 的静态工厂方法
,可以很直接明了的让调用者知道返回的是一个什么玩意; - 可以控制返回对象的个数;
- 可以灵活的返回原对象类型的子类型;
- 可以简化泛型的书写,在自己的工具类中应该有这个小工具;
缺点
不利于扩展
类的构造器被
私有化
了,或者类是非public
,那么我们就不能复用
它们,就像Collection
框架,我们想对同步集合进行复用,直接GG,是非public
类的,跨包访问不到;继承
了,是不好使,只能使用返回的子类型对象,进行组合
(因祸得福 ?!);静态工厂方法不容易被识别出来
静态工厂方法,归根到底也就是普通的静态方法,不能向构造器那样,一下子被
javaDoc
识别出来,使用者往往需要寻找半天才能找到需要的那个静态工厂方法,而不能向构造器那样,一下定位到;我们可以为它们取一些,大家认可的名字,来帮助定位;比如
valueOf
、newInstance
、getInstance
等等 ;