利用Kotlin中的伴生对象增强工厂模式
一些地方会把工厂模式细分为简单工厂、工厂方法模式以及抽象工厂,一下分类说明:
简单工厂
简单工厂的核心作用就是通过一个工厂类隐藏对象实例的创建逻辑,而不是暴露给客户端。典型的使用场景就是当拥有一个父类与多个子类的时候,我们可以通过这种模式来创建子类对象。
例如现在有一个电脑加工厂,同时生产个人电脑和服务器主机。用java中的思维逻辑来实现工厂模式
interface Computer { val cpu: String }
class PC(override val cpu: String = "Core"): Computer
class Server(override val cpu: String = "Xeon"): Computer
enum class ComputerType { PC,Server }
class ComputerFactory { fun produce(type: ComputerType): Computer { return when(type) { ComputerType.PC -> PC() ComputerType.Server -> Server() } } }
|
以上代码通过调用ComputerFactory类的produce方法来创建不同的Computer子类对象,这样我们就把创建实例的逻辑与客户端之间实现解耦,当对象创建的逻辑发生变化时,该模式只需要修改produce函数内部的代码即可,相比直接创建对象的方法更加利于维护。
虽然它改善了程序的可维护性,但创建对象的的表达上却显得不够简洁。当我们在不同的地方创建Computer的子类对象时,我们都需要先创建一个ComputerFactory类对象,而Kotlin天生支持了单例,接下来我们就用object关键字以及相关的特性来进一步简化以上的代码设计。
用单例代替工厂类
Kotlin支持用object来实现Java中的单例模式,所以我们可以实现一个ComputerFactory单例,而不是一个工厂类。
object ComputerFatory { fun produce(type: ComputerType): Computer { return when(type) { ComputerType.PC -> PC() ComputerType.Server -> Server() } } }
ComputerFactory.produce(ComputerType.PC)
|
此外,由于我们通过传入Computer类型来创建不同的对象,所以这里的produce又显得多余,而Kotlin支持运算符重载,因此我们可以通过operator操作符重载invoke方法来代替produce,从而进一步简化表达:
object ComputerFactory { operator fun invoke(type: ComputerType): Computer { return when(type) { ComputerType.PC -> PC() ComputerType.Server -> Server() } } }
ComputerFactory(ComputerType.PC)
|
依靠Kotlin这一特性,我们再创建一个Computer对象就显得非常简洁,与直接创建一个具体类实例显得没有太大区别。
伴生对象创建静态工厂方法
当前的工厂模式实现已经足够优雅,而你依旧觉得不够完美:我们是否可以直接通过Computer()而不是ComputerFactory()来创建一个实例呢?
《Effective Java》一书的第一条指导原则:考虑用静态工厂方法代替构造器。
Kotlin中的伴生对象,代替了Java中的static,同时在功能和表达上拥有更强的能力。通过在Computer接口中定义一个伴生对象,我们就能实现以上的需求:
interface Computer { val cpu: String companion object Factory { operator fun invoke(type: ComputerType): Computer { return when(type) { ComputerType.PC -> PC() ComputerType.Server -> Server() } } } }
Computer.Factory(ComputerType.PC)
|
扩展伴生对象方法
依赖伴生对象的特性,已经实现了经典的工厂模式。同时这种方式还有一个优势,它比原有Java中的设计更加强大。假如实际业务中我们是Computer借口的使用者,比如它是工程引入的第三方类库,所有类的实现细节都得到了很好地隐藏。那么,如果希望进一步改造其中的逻辑,Kotlin中伴生对象的方式同样可以依靠其扩展函数的特性,很好地实现这一需求。
比如我们需要给Computer添加一种功能,通过CPU型号来判断电脑类型,那么久可以如下实现:
fun Computer.Companion.fromCPU(cpu: String): ComputerType? { return when (cpu) { "Core" -> ComputerType.PC "Xeon" -> ComputerType.Server else -> null } }
|
如果指定了伴生对象的名字为Factory,那么就可以如下实现:
fun Computer.Factory.fromCPU(cpu: String): ComputerType? = ...
|
内联函数简化抽象工厂
工厂模式已经能够很好地处理一个产品等级结构的问题,上述简单工厂已经解决了电脑厂商生产服务器、PC机的问题。进一步思考,当问题上升到多个产品等级结构的时候,比如现在引入了品牌商的概念,我们有好几个不同的电脑品牌,比如Dell,Asus,Acer,那么就有必要再增加一个工厂类。然而,我们并不希望对每个模型都建立一个工厂,这会让代码变得难以维护,所以这时候需要引入抽象工厂模式。
抽象工厂模式
为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。
在抽象工厂的定义中,我们也可以把“一组相关或相互依赖的对象”称作“产品族”,在上述的例子中,我们就提到了3个代表不同电脑品牌的产品族。下面我们就利用抽象工厂,来实现具体需求:
interface Computer class Dell: Computer class Asus: Computer class Acer: Computer
class DellFactory: AbstractFactory() { override fun produce() = Dell() } class AsusFactory: AbstractFactory() { override fun produce() = Asus() } class AcerFactory: AbstractFactory() { override fun produce() = Acer() }
abstract class AbstractFactory { abstract fun produce(): Computer companion object { operator fun invoke(factory: AbstractFactory) = factory } }
AbstractFactory(DellFactory()).produce()
|
每个电脑品牌拥有一个代表电脑产品的类,它们都实现了Computer接口。此外每个品牌也还有一个用于生产电脑的AbstractFactory子类,可通过AbstractFactory类的伴生对象中的invoke方法,来构造具体品牌的工厂类对象。
由于Kotlin语法的简洁,以上例子的抽象工厂类的设计也比较直观。然而,当你每次创建具体的工厂类时,都需要传入一个具体的工厂类对象作为参数进行构造,这个在语法上显然不是很优雅。所以,我们可以用内联函数来改善这一情况:
abstract class AbstractFactory { abstract fun produce(): Computer companion object { inline operator fun <reified T : Computer> invoke() = when(T::class) { Dell::class -> DellFactory() Asus::class -> AsusFactory() Acer::class -> AcerFactory() else -> throw IllegalArgumentException() } } }
AbstractFactory<Dell>().produce()
|
- 通过将invoke方法定义为内联函数,我们就可以引入reified关键字,使用具体化参数类型的语法特性
- 要具体化的参数类型为Computer,在invoke方法中我们通过判断它的具体类型,来返回对于的工厂类对象
参考: Dive in Kotlin