设计模式(Kotlin实现):单例、工厂、观察者、策略、建造者
设计模式这东西,说白了就是前辈们踩坑踩出来的「最佳实践」。我刚开始写Kotlin时,总觉得这些模式是Java时代的产物,Kotlin这么现代的语言还用得着吗?后来在项目里被反复毒打,才明白——语言再先进,架构设计的思想是不会过时的。
今天咱们就聊聊Kotlin中五个最常用的设计模式。我会结合自己的项目经验,把每个模式的实现要点和坑都讲清楚。
一、单例模式:Kotlin最优雅的实现
单例模式,保证一个类只有一个实例。Java里写单例,要考虑线程安全、双重检查锁、volatile关键字……麻烦得很。
但在Kotlin里,一个object关键字就搞定了:
object NetworkManager {
private val client = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.build()
fun request(url: String): String {
// 网络请求逻辑
return client.execute(url)
}
}
// 使用
NetworkManager.request("https://api.example.com/data")
嗯,这里要注意——object是线程安全的,Kotlin编译器会帮我们处理好。我在项目中用这种方式管理全局配置、网络客户端、数据库实例,从来没出过问题。
companion object配合getInstance()方法,而不是直接用object。这样更灵活。
class DatabaseHelper private constructor(context: Context) {
companion object {
@Volatile
private var instance: DatabaseHelper? = null
fun getInstance(context: Context): DatabaseHelper {
return instance ?: synchronized(this) {
instance ?: DatabaseHelper(context.applicationContext).also { instance = it }
}
}
}
}
我曾经在早期项目里直接用object做数据库管理,结果单元测试时发现实例无法重置,数据互相污染。后来改成带参数的懒加载单例,测试就好写多了。
二、工厂模式:把创建逻辑封装起来
工厂模式的核心思想:把「创建对象」这件事抽象出来。调用方不需要知道具体创建细节,只需要告诉工厂「我要什么」。
Kotlin里实现工厂模式,我推荐用sealed class + companion object:
sealed class Payment {
data class Alipay(val orderId: String) : Payment()
data class WechatPay(val orderId: String) : Payment()
data class CreditCard(val cardNumber: String) : Payment()
companion object {
fun create(type: String, orderId: String): Payment {
return when (type) {
"alipay" -> Alipay(orderId)
"wechat" -> WechatPay(orderId)
"credit" -> CreditCard(orderId)
else -> throw IllegalArgumentException("Unknown payment type: $type")
}
}
}
}
// 使用
val payment = Payment.create("alipay", "20240101001")
你想想看,如果不用工厂模式,每次创建支付对象都要写when分支,代码散落在各处,维护起来多痛苦。工厂模式把变化集中到一处,改起来也方便。
三、观察者模式:事件驱动的基石
观察者模式,说白了就是「发布-订阅」。一个对象状态变化,通知所有依赖它的对象。
Kotlin标准库提供了Observable和Vetoable委托,但说实话,我在项目中更常用Flow或LiveData来实现观察者模式。不过为了讲清楚原理,咱们还是手写一个:
interface Observer {
fun update(data: String)
}
class Subject {
private val observers = mutableListOf<Observer>()
fun attach(observer: Observer) {
observers.add(observer)
}
fun detach(observer: Observer) {
observers.remove(observer)
}
fun notifyObservers(data: String) {
observers.forEach { it.update(data) }
}
}
// 具体观察者
class ConsoleObserver(private val name: String) : Observer {
override fun update(data: String) {
println("[$name] Received: $data")
}
}
// 使用
val subject = Subject()
val observer1 = ConsoleObserver("Observer1")
val observer2 = ConsoleObserver("Observer2")
subject.attach(observer1)
subject.attach(observer2)
subject.notifyObservers("Hello, World!")
实际项目中,我建议直接用Kotlin的Flow或者Android的LiveData。手写观察者模式容易出内存泄漏——观察者注册了忘记注销,对象就永远无法被回收。
WeakReference包装观察者,或者使用生命周期感知的组件。
四、策略模式:算法自由切换
策略模式,把不同的算法封装成独立的策略类,让它们可以互相替换。调用方只需要知道策略接口,不需要关心具体实现。
Kotlin的函数式特性让策略模式实现起来特别简洁:
// 策略接口
interface SortStrategy {
fun sort(list: List<Int>): List<Int>
}
// 具体策略
class BubbleSort : SortStrategy {
override fun sort(list: List<Int>): List<Int> {
// 冒泡排序实现
return list.sorted()
}
}
class QuickSort : SortStrategy {
override fun sort(list: List<Int>): List<Int> {
// 快速排序实现
return list.sorted()
}
}
// 上下文
class Sorter(private var strategy: SortStrategy) {
fun setStrategy(strategy: SortStrategy) {
this.strategy = strategy
}
fun executeSort(list: List<Int>): List<Int> {
return strategy.sort(list)
}
}
// 使用
val sorter = Sorter(BubbleSort())
sorter.executeSort(listOf(3, 1, 4, 1, 5, 9))
sorter.setStrategy(QuickSort())
sorter.executeSort(listOf(3, 1, 4, 1, 5, 9))
其实Kotlin里还可以用高阶函数进一步简化:
class Sorter(private val strategy: (List<Int>) -> List<Int>) {
fun executeSort(list: List<Int>): List<Int> = strategy(list)
}
// 使用
val bubbleSort: (List<Int>) -> List<Int> = { it.sorted() }
val quickSort: (List<Int>) -> List<Int> = { it.sorted() }
val sorter = Sorter(bubbleSort)
sorter.executeSort(listOf(3, 1, 4, 1, 5, 9))
我个人习惯用函数类型代替接口,代码更简洁。但如果是大型项目,接口方式更清晰,便于团队协作。
五、建造者模式:构建复杂对象
建造者模式,解决的是「构造参数太多」的问题。Java里写建造者,要写一堆Builder内部类,代码量很大。Kotlin有命名参数和默认参数,大部分场景不需要建造者模式。
但有些场景还是需要的——比如构建不可变对象,或者参数之间有依赖关系:
class HttpRequest private constructor(
val url: String,
val method: String,
val headers: Map<String, String>,
val body: String?,
val timeout: Long
) {
class Builder {
private var url: String = ""
private var method: String = "GET"
private var headers: Map<String, String> = emptyMap()
private var body: String? = null
private var timeout: Long = 5000L
fun url(url: String) = apply { this.url = url }
fun method(method: String) = apply { this.method = method }
fun headers(headers: Map<String, String>) = apply { this.headers = headers }
fun body(body: String?) = apply { this.body = body }
fun timeout(timeout: Long) = apply { this.timeout = timeout }
fun build(): HttpRequest {
require(url.isNotBlank()) { "URL must not be blank" }
return HttpRequest(url, method, headers, body, timeout)
}
}
}
// 使用
val request = HttpRequest.Builder()
.url("https://api.example.com/data")
.method("POST")
.headers(mapOf("Content-Type" to "application/json"))
.body("{\"key\":\"value\"}")
.timeout(10000L)
.build()
你可能会问:Kotlin有apply作用域函数,还需要建造者模式吗?我的经验是:如果对象构造逻辑简单,用apply就够了;如果构造过程需要校验、默认值、参数依赖,建造者模式更合适。
知识体系总览
下面这张图总结了五种设计模式的核心要点和Kotlin实现方式:
总结一下
设计模式不是银弹,用对了是利器,用错了是累赘。我的经验是:
- 单例模式:Kotlin的
object是最佳实践,需要参数时用companion object+ 双重检查锁 - 工厂模式:用
sealed class+companion object,把创建逻辑集中管理 - 观察者模式:优先用
Flow或LiveData,手写时注意内存泄漏 - 策略模式:Kotlin的高阶函数可以简化实现,但大型项目还是用接口更清晰
- 建造者模式:先考虑命名参数+默认值,参数复杂时再用Builder
记住:设计模式是为了解决特定问题而存在的。不要为了用模式而用模式,先想清楚「我要解决什么问题」,再选择合适的模式。