前言
获取一个类的实例,除了使用其本身的构造器外,还可以使用静态工厂方法。
静态工厂方法的应用非常广泛,例如:对于一些工具类而言,在仅仅是想调用类的方法,却又不想为此创建一个实例(增加无意义的开销)的时候;在只需要一个实例存在,并全局都能访问到的时候;在创建并返回一个实例前做一些额外工作的时候,都可以利用静态工厂方法来达到目的。
接下来就通过三个例子具体说明静态工厂方法的应用。
在工具类中的应用
应用程序在运行时,一般需要将一些数据缓存到手机中,以便下次直接使用。为了方便地获取到应用程序的缓存路径,可以定义一个工具类 CacheDirManager
:
|
CacheDirManager
类提供了两个公有的静态方法,方便调用时直接返回缓存路径,避免创建不必要的对象。
然而仅仅是这样还不够,开发人员还是能够通过调用系统提供的默认构造器获取 CacheDirManager
的实例。因此将其构造器设为私有,防止它被实例化:
|
注:因类不含有公有的或者受保护的构造器,所以不能被子类化,推荐使用组合的方式而不是继承。
在使用单例场景中的应用
单例作为静态工厂中的一种,在 Android 开发过程中也常被用到。特别适用于每次程序启动时只会存在一个实例,并且需要被频繁访问的场景,例如:数据库对象 SQLiteDatabase
和 Android 上的轻量存储类 SharedPreferences
。
使用 SharedPreferences
有诸多限制,每次调用前必须拿到 Context
,而且也不是线程安全的。下面通过定义一个 PrefHelper
工具类来更方便地使用 SharedPreferences
。
|
上面的写法看上去没什么问题,只有在第一次调用静态方法时才创建实例。但如果多个线程同时访问时,很可能出现因状态不同步而重复创建多个实例的情况。
另一种写法是在第一次引用该类的时候就创建实例,不管实际是否需要创建:
|
这样写的好处就是在第一次引用 PrefHelper
时,其实例 sInstance
就已经创建好了,不必等到调用静态方法时才创建,因此是线程安全的。但很多时候我们还是只希望在调用单例方法时才创建实例以减小负载,所以还是接着改进第一种方法。
考虑到线程安全,修改静态方法:
|
加入同步锁后解决了线程安全的问题,但是每次调用时都要检查一次同步锁,效率略低。所以在检查同步锁之前再加一层判断:
|
如果实例已经创建了,无须再加同步锁。这种写法应该很完美了吧?只在需要时才创建实例,加入同步锁保证线程安全,在加锁前对 sInstance
进行 null
检查,提高了效率。但问题出现在这里:
|
编译器在编译时会对指令进行重排优化,在实际执行时可能与编写时的顺序不同。所以可能会发生 sInstance
还没完成初始化完就被分配了内存空间。因此需要在声明 sInstance
前加入关键字 volatile
。
|
当一个变量被定义为 volatile
之后,将具备两种特性:第一保证此变量对所有的线程的可见性;第二禁止指令重排序优化。进而避免了因编译器重排指令而导致的上述问题。
在 Fragment 中的应用
为了获取 Fragment 实例,我们可以直接调用系统为其提供的默认构造器:
|
如果在创建实例前,需要向 Fragment 传递参数,可以创建一个含参构造器:
|
看似很正常,但在一些特殊情况下会出现问题:在屏幕旋转时、开启分屏模式时( Android 7.0 及以上支持的特性),Fragment 会伴随着 Activity 销毁重建。此时 Fragment 只会调用默认的无参构造器,之前传递的参数无法保存。
官方提倡的方法是利用静态工厂方法和 Bundle 传递数据:
|
此时调用者只需要关心传递哪些参数,即使在 Activity 重建之后,系统也会自动帮你恢复之前保存的数据。