美文网首页
给人看的Kotlin设计模式——简单工厂

给人看的Kotlin设计模式——简单工厂

作者: 珞泽珈群 | 来源:发表于2020-04-18 18:03 被阅读0次

给人看的Kotlin设计模式目录

概念

简单工厂模式相对正式的定义:

In object-oriented programming (OOP), a factory is an object for creating other objects – formally a factory is a function or method that returns objects of a varying prototype or class from some method call, which is assumed to be "new". (Wikipedia)

说人话就是,用一个称之为“工厂”的方法去封装对象的创建过程,这样我们就不需要关心对象创建的细节,只需要调用“工厂”方法,然后获得对象。So easy,妈妈再也不用担心我没有对象了。

核心思想:对象创建与对象使用分离。

例如:

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE; //不会创建新对象,永远只有两个对象
}

这是 Effective Java 的第一个例子,这个方法将boolean基本类型值转换成了Boolean对象引用,这当然算是一个“简单工厂”。其实简单工厂模式对应了 Effective Java 的第一条用静态工厂方法代替构造器,Effective Java还罗列了这么做的五大优势:

  1. 静态工厂方法相较于构造器而言,它们有名称
  2. 不必在每次调用它们的时候都创建一个新对象
  3. 可以返回原返回类型的任何子类型的对象
  4. 所返回的对象的类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值
  5. 方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在(例如使用动态代理技术)

这些也是简单工厂模式的优势,或者说是简单工厂模式适用的情形。就是这么一个看似简单的设计模式,却在Kotlin中有非常多地道地应用。

Kotlin的应用

众所周知,Kotlin是“没有”静态方法这一说的,简单工厂模式强调的只是有这么一个方法,隐藏了构造对象的过程,只不过落实到Java上往往是静态工厂方法承担起了这一责任。对于Kotlin而言,承担这一责任的可以有非常多的选择:

  1. 伴生对象
  2. 顶层函数
  3. 伪构造器

伴生对象与简单工厂

对于Java静态方法的直接替代,自然要说是Kotlin中的伴生对象,我们当然可以用伴生对象去直接替代Java中的静态工厂方法,这毫无新意,然而,伴生对象的好处当然不仅仅是对静态方法的替代,伴生对象最大的优势在于它可以继承其它类(因为伴生对象在底层实现上其实是一个单例对象),这就使得我们可以使用继承的方式来强化伴生对象对于简单工厂模式的支持:

abstract class Provider<T> {
     var original: T? = null
     var mocked: T? = null
     abstract fun create(): T
     fun get(): T = mocked ?: original ?: create()
           .apply { original = this }
     fun lazyGet(): Lazy<T> = lazy { get() }
}

interface UserRepository {
    fun getUser(): User
    //伴生对象实现了 Provider
    companion object : Provider<UserRepository>() {
        override fun create() = UserRepositoryImpl()
    }
}

可以通过UserReposiroty.get()方法获取UserReposiroty对象,或者通过val repo by UserRepository.lazyGet()这种懒加载方式获得相应的实例对象。也可以为测试指定不同的对象来实现模拟测试的需求。

在MvRx中也有这样的应用:

class MyViewModel(initialState: MyState, dataStore: DataStore) : BaseMvRxViewModel(initialState) {

    //如果要实现依赖注入,要求 MyViewModel的伴生对象必须实现 MvRxViewModelFactory
    companion object : MvRxViewModelFactory<MyViewModel, MyState> {
        override fun create(viewModelContext: ViewModelContext, state: MyState): MyViewModel {
            //...
            return MyViewModel(state, dataStore)
        }

    } 
}

显然MvRxViewModelFactory是一个构造ViewModel的工厂类,强制要求伴生对象实现MvRxViewModelFactory,也就是强制我们的伴生对象必须实现工厂方法create

从形式上讲,我们还是使用UserReposiroty.get()MyViewModel.create()方法来获得实例对象,这是简单工厂模式;但是从实现上讲,我们其实是用伴生对象来实现了特定的工厂方法,这已经是工厂方法模式了。可以这么说,伴生对象让简单工厂模式和工厂方法模式在Kotlin上实现了统一。

还可以通过为伴生对象添加扩展方法的方式来实现简单工厂:

interface Tool {
   companion object { … }
}
//在伴生对象上定义扩展方法来实现简单工厂
fun Tool.Companion.createBigTool(…) : BigTool { … }

调用Tool.createBigTool()方法就好像Tool上原本就定义了这么一个工厂方法一样。之所以不直接在Tool的伴生对象内部定义createBigTool工厂方法可能有如下的原因:
1.想在另外一个文件定义createBigTool方法
2.Tool来源于外部依赖库

顶层函数与简单工厂

Kotlin上的简单工厂模式是如此的常见,以致于我们熟视无睹。listOf,arrayOf,mapOf等都是简单工厂模式,就是这么常见,这些都是使用Kotlin顶层函数实现的简单工厂。以listOf为例:

public fun <T> emptyList(): List<T> = EmptyList

public fun <T> listOf(vararg elements: T): List<T> = if (elements.size > 0) elements.asList() else emptyList()

public inline fun <T> listOf(): List<T> = emptyList()

显然listOf(1,2,3)List.of(1,2,3)更简单并且更具有可读性,使用顶层函数实现简单工厂模式是创建小型、常用对象的完美选择。但是也正是因为顶层函数简单易用,所以应该避免语意不明的滥用。Kotlin顶层函数在JVM上会被编译为类的静态方法,所以说Java静态工厂方法在Kotlin上最直接的对应应该是顶层函数。

伪构造器与简单工厂

Effective Java 中提到静态工厂方法最主要的缺点就是——程序员很难发现它们。很神奇啊,我们列举了静态工厂方法的种种优势但是最大的问题竟然是使用者不知道,或者忘记去使用这些方法,所以静态工厂方法往往遵循一些惯用的名称——from,of,create,newInstance,getInstance,valueOf 等等。在Kotlin中我们可以尽量避免这种情况发生,因为我们有“伪构造器”。

伪构造器其实就是跟类同名的顶层函数,一般而言方法或者函数不允许以大写字母开头,而类名要以大写字母开头,但是如果我们定义一个顶层函数,它就是以某个类名作为自己的函数名,那么这个函数看上去就跟构造函数是一样的。其实,这种函数在Kotlin中很常见,只不过我们又熟视无睹了:

//注意,这是一个名为 List的顶层函数,并不是一个构造函数
public inline fun <T> List(size: Int, init: (index: Int) -> T): List<T> = MutableList(size, init)

public inline fun <T> MutableList(size: Int, init: (index: Int) -> T): MutableList<T> {
    val list = ArrayList<T>(size)
    repeat(size) { index -> list.add(init(index)) }
    return list
}

在Kotlin中List是一个接口,我们自然无法实例化对象,但是Kotlin标准库中定义了一个同名的顶层函数,所以我们可以这样使用List(3) { "$it" },等同于listOf("0", "1", "2")。我就跟List同名,但是我是一个工厂方法,你想假装不知道都不行。客户端其实并不关心这个函数究竟是工厂方法还是构造函数,只要它能起到构造函数的作用就可以了,这也提醒我们一定不要定义一个伪的伪构造器(与类同名的函数却不起构造函数的作用),这会引起巨大的迷惑(应该也没人这么干)。

如果我们自己定义伪构造器,会提示你函数名不合适,最好加上注解:

@Suppress("FunctionName")
public fun Job(parent: Job? = null): CompletableJob = JobImpl(parent)

虽说伪构造器可以防止客户端不知道工厂方法的存在,但是也不能滥用。伪构造器一般都是用在接口/抽象类上,作为某个接口/抽象类的简单工厂,返回接口的实现类对象;或者是需要使用reified功能。对于普通类而言,具有更好语义的顶层函数应该是实现简单工厂模式的首选。

总结

看来简单工厂模式也不简单啊,Kotlin实现简单工厂模式首推顶层函数的方式,这种方式简单,可读性也更高,并且可以用“伪构造器”的方式来隐藏,简直是神不知鬼不觉。如果想要以接口的形式强化工厂方法的实现(例如用于依赖注入),可以考虑使用伴生对象的方式,这是简单工厂模式和工厂方法模式的一种折中。

本文参考了Effective Java in Kotlin, item 1

相关文章

网友评论

      本文标题:给人看的Kotlin设计模式——简单工厂

      本文链接:https://www.haomeiwen.com/subject/xsqwvhtx.html