创建型模式
主流的创建型模式有:工厂方法模式、抽象工厂模式、构建者模式
伴生对象增强工厂模式
在有些地方会把工厂模式细分为简单工厂、工厂方法模式以及抽象工厂。
这里主要介绍简单工厂的模式,它的核心作用就是通过一个工厂类隐藏对象实例的创建逻辑,而不需要暴露给客户端。典型的使用场景就是当拥有一个父类与多个子类的时候,我们可以通过这种模式来创建子类对象。
假设现在有一个电脑加工厂,同时生产个人电脑和服务器主机。我们用熟悉的工厂模式设计描述其业务逻辑:
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()
}
}
}
fun main() {
val compter = ComputerFactory().produce(ComputerType.PC)
println(compter.cpu)
}
以上代码通过调用ComputerFactory
类的produce
方法来创建不同的Computer
子类对象,这样我们就把创建实例的逻辑与客户端之间实现解耦,当对象创建的逻辑发生变化时(如构造参数的数量发生变化),该模式只需要修改produce方法内部的代码即可,相比直接创建对象的方式更加利于维护。
用单例代替工厂类
我们已经知道的是,Kotlin 支持用 object
来实现 Java 中的单例模式。所以,我们可以实现一个 ComputerFactory
单例,而不是一个工厂类。
object ComputerFactory {
// 用 object 代替 class
fun produce(type: ComputerType): Computer {
return when (type) {
ComputerType.PC -> PC()
ComputerType.Server -> Server()
}
}
}
fun main() {
val compter = ComputerFactory.produce(ComputerType.PC)
println(compter.cpu)
}
我们可以通过operator
操作符重载invoke
方法来代替produce
,从而进一步简化表达:
object ComputerFactory {
operator fun invoke(type: ComputerType): Computer {
return when (type) {
ComputerType.PC -> PC()
ComputerType.Server -> Server()
}
}
}
fun main() {
val compter = ComputerFactory(ComputerType.PC)
println(compter.cpu)
}
伴生对象创建静态工厂方法
我们是否可以直接通过 Computer()
而不是 ComputerFactory()
来创建一个实例呢?
考虑用静态工厂方法代替构造器。相信你已经想到了 Kotlin 中的伴生对象,它代替了 Java 中的static
,同时在功能和表达上拥有更强的能力。通过在 Computer
接口中定义一个伴生对象,我们就能够实现以上的需求,代码如下:
interface Computer {
val cpu: String
companion object {
operator fun invoke(type: ComputerType): Computer {
return when (type) {
ComputerType.PC -> PC()
ComputerType.Server -> Server()
}
}
}
}
class PC(override val cpu: String = "Core") : Computer
class Server(override val cpu: String = "Xeon") : Computer
enum class ComputerType {
PC, Server }
fun main() {
val compter = Computer(ComputerType.PC)
println(compter.cpu)
}
在不指定伴生对象名字的情况下,我们可以直接通过 Computer
来调用其伴生对象中的方法。当然,如果你喜欢伴生对象有名字,我们还是可以命名 Computer
的伴生对象,如下用 Factory
来命名:
interface Computer {
val cpu: String
companion object Factory {
operator fun invoke(type: ComputerType): Computer {
return when (type) {
ComputerType.PC -> PC()
ComputerType.Server -> Server()
}
}
}
}
fun main() {
val compter = Computer.Factory(ComputerType.PC)
println(compter.cpu)
}
注意:即便伴生对象是有名字的情况下,在调用时依然可以省略显示指定的名字。
扩展伴生对象方法
假设实际业务中我们是Computer
接口的使用者,比如它是工程引入的第三方类库,所有的类的实现细节都得到了很好地隐藏。那么,如果我们希望进一步改造其中的逻辑,Kotlin 中伴生对象的方式同样可以依靠其扩展函数的特性,很好地实现这一需求。
比如我们希望给Computer
增加一种功能,通过CPU型号来判断电脑类型,那么就可以如下实现:
fun Computer.Companion.fromCPU(cpu: String): ComputerType? = when(cpu) {
"Core" -> ComputerType.PC
"Xeon" -> ComputerType.Server
else -> null
}
如果指定了伴生对象的名字为Factory
,那么就可以如下实现:
fun Computer.Factory.fromCPU(cpu: String): ComputerType? = when(cpu) {
"Core" -> ComputerType.PC
"Xeon" -> ComputerType.Server
else -> null
}
调用:
fun main() {
val type = Computer.fromCPU("Core")
println(type)
}
内联函数简化抽象工厂
Kotlin中 的内联函数有一个很大的作用,就是可以具体化参数类型。利用这一特性,我们还可以改进一种更复杂的工厂模式,称为抽象工厂。
工厂模式已经能够很好地处理一个产品等级结构的问题,在上一节中,我们已经用它很好地解决了电脑厂商生产服务器、PC机的问题。进一步思考,当问题上升到多个产品等级结构的时候,比如现在引入了品牌商的概念,我们有好几个不同的电脑品牌,比如 Dell、Asus、Acer,那么就有必要再增加一个工厂类。然而,我们并不希望对每个模型都建立一个工厂,这会让代码变得难以维护,所以这时候我们就需要引入抽象工厂模式。
抽象工厂模式
为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。
在抽象工厂的定义中,我们也可以把“ 一组相关或相互依赖的对象” 称作 “产品族”,在上述的例子中,我们就提到了3个代表不同电脑品牌的产品族。
下面我们就利用抽象工厂,来实现具体的需求:
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): AbstractFactory {
return factory
}
}
}
fun main() {
val dellFactory = AbstractFactory(DellFactory())
val dell = dellFactory.produce()
println(dell)
}
以上代码当你每次创建具体的工厂类时,都需要传入一个具体的工厂对象作为参数进行构造,这个在语法上显然不是很优雅。
下面我们可以用 Kotlin 中的内联函数来改善这一情况。我们所需要做的,就是去重新实现 AbstractFactory
类中的 invoke
方法。
abstract class AbstractFactory {
abstract fun produce(): Computer
companion object {
inline operator fun <reified T : Computer> invoke(): AbstractFactory = when(T::class) {
Dell::class -> DellFactory()
Asus::class -> AsusFactory()
Acer::class -> AcerFactory()
else -> throw IllegalArgumentException()
}
}
}
fun main() {
val dellFactory = AbstractFactory<Dell>()
val dell = dellFactory.produce()
println(dell)
}
- 1)通过将
invoke
方法用inline
定义为内联函数,我们就可以引入reified
关键字,使用具体化参数类型的语法特性; - 2)要具体化的参数类型为
Computer
,在invoke
方法中我们通过判断它的具体类型,来返回对应的工厂类对象。
用具名可选参数而不是构建者模式
在 Java 开发中,你是否写过这样像蛇一样长的构造函数:
// Boolean 类型的参数表示 Robot 是否含有对应固件
Robot robot = new Robot(1, true, true, false, false, false, false, false, false)
刚写完时回头看你还能看懂,一天后你可能已经忘记大半了,再过一个星期你已经不知道这是什么东西了。面对这样的业务场景时,我们惯常的做法是通过 Builder(构建者)模式来解决。
构建者模式
构建者模式主要做的事情就是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
工厂模式和构造函数都存在相同的问题,就是不能很好地扩展到大量的可选参数。假设我们现在有个机器人类,它含有多个属性:代号、名字、电池、重量、高度、速度、音量等。很多产品都不具有其中的某些属性,比如不能走、不能发声,甚至有的机器人也不需要电池。
一种糟糕的做法就是设计一个在上面你所看到Robot
类,把所有的属性都作为构造函数的参数。或者,你也可能采用过重叠构造器(telescoping constructor)模式,即先提供一个只有必要参数的构造函数,然后再提供其他更多的构造函数,分别具有不同情况的可选属性。虽然这种模式在调用的时候改进不少,但同样存在明显的缺点。因为随着构造函数的参数数量增加,很快我们就会失去控制,代码变得难以维护。
构建者模式可以避免以上的问题,我们用 Kotlin 来实现 Java 中的构建者模式:
class Robot private constructor(
val code