工厂方法模式

利用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

作者

8MilesRD

发布于

2020-01-17

更新于

2020-09-17

许可协议

评论