设计模式(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标准库提供了ObservableVetoable委托,但说实话,我在项目中更常用FlowLiveData来实现观察者模式。不过为了讲清楚原理,咱们还是手写一个:

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中优先使用命名参数+默认参数。只有当参数超过5个,或者参数之间有复杂的校验逻辑时,才考虑建造者模式。

知识体系总览

下面这张图总结了五种设计模式的核心要点和Kotlin实现方式:

Kotlin 设计模式核心知识体系 单例模式 • object 关键字 • 线程安全,懒加载 • companion object 带参数 • 适合:全局配置、网络客户端 工厂模式 • sealed class + companion • 封装创建逻辑 • 集中管理变化 • 适合:支付、消息推送 观察者模式 • 发布-订阅机制 • Flow / LiveData 实现 • 注意内存泄漏 • 适合:事件总线、UI更新 策略模式 • 算法自由切换 • 接口或高阶函数 • 函数式简化实现 • 适合:排序、校验、计算 建造者模式 • 构建复杂对象 • 参数校验与依赖 • 优先命名参数+默认值 • 适合:HTTP请求、配置对象 核心原则 • 开闭原则:对扩展开放 • 单一职责:每个类只做一件事 • 依赖倒置:面向接口编程 • 组合优于继承 Kotlin 让设计模式实现更简洁,但核心思想不变

总结一下

设计模式不是银弹,用对了是利器,用错了是累赘。我的经验是:

  • 单例模式:Kotlin的object是最佳实践,需要参数时用companion object + 双重检查锁
  • 工厂模式:用sealed class + companion object,把创建逻辑集中管理
  • 观察者模式:优先用FlowLiveData,手写时注意内存泄漏
  • 策略模式:Kotlin的高阶函数可以简化实现,但大型项目还是用接口更清晰
  • 建造者模式:先考虑命名参数+默认值,参数复杂时再用Builder

记住:设计模式是为了解决特定问题而存在的。不要为了用模式而用模式,先想清楚「我要解决什么问题」,再选择合适的模式。

最后说一句:代码是写给人看的,顺便能在机器上运行。设计模式让代码更容易理解、更容易维护,这才是它的价值所在。

公众号:蓝海资料掘金营,微信deep3321