设计模式大全(GoF 23)

4 阅读15分钟

1. GoF 设计模式总览

GoF(Gang of Four)提出了 23 种经典设计模式,分三类:

  • 创建型(5):对象怎么创建更灵活
  • 结构型(7):类和对象怎么组合更合理
  • 行为型(11):对象之间怎么协作更清晰

1.1 23 模式脑图(文字版)

  • 创建型:SingletonFactory MethodAbstract FactoryBuilderPrototype
  • 结构型:AdapterBridgeCompositeDecoratorFacadeFlyweightProxy
  • 行为型:ChainCommandInterpreterIteratorMediatorMementoObserverStateStrategyTemplate MethodVisitor

1.2 统一业务主线(建议你这样代入理解)

阅读时尽量把示例映射到同一套业务语境:

  • 订单域:状态、策略、责任链、命令
  • 支付域:工厂、适配器、代理
  • 通知域:观察者、装饰器、外观

这样你不会把 23 个模式学成 23 个孤岛。


2. 创建型模式(5)

本类模式特点速记(创建型)

模式核心特点(详细)代码识别信号记忆点
单例全局唯一实例,适合承载全局配置与共享资源;生命周期通常随进程;需注意可变状态并发安全object / 私有构造 + 全局 instance一个系统一个我
工厂方法将“创建谁”延迟到子类,调用方依赖抽象;新增实现时改动小,符合开闭原则工厂层级通常围绕一个产品创建方法(如 create()一个工厂造一个产品层级
抽象工厂一次创建一整套关联对象,强调产品族一致性;适合多主题、多平台同一工厂接口有多个 createXxx()一个工厂造一套
建造者分步骤构建复杂对象,支持可选参数、校验、链式调用;可读性高Builder + build() + apply先装配再出厂
原型通过复制已有对象提效,适合初始化成本高对象;重点是深浅拷贝边界copy/clone/deepCopy以旧生新

2.1 单例(Singleton)

解决问题:某类对象在系统中应当只有一个实例(如配置中心、日志中心、进程级缓存入口)。
核心思路:隐藏构造方法,统一暴露全局访问点。

方式1:Kotlin object(首选)

object AppConfig {
    var env: String = "prod"
    fun isProd() = env == "prod"
}

解读

  • object 天然单例,语义最清晰。
  • Kotlin 下默认优先这一种。

方式2:Kotlin 饿汉式(伴生对象成员首次访问即初始化)

class EagerSingleton private constructor() {
    companion object {
        val instance: EagerSingleton = EagerSingleton()
    }
}

解读

  • instance 在首次访问伴生对象成员时初始化(不是业务意义上的按需懒策略)。
  • 优点是简单、线程安全;缺点是控制粒度不如 by lazy 灵活。

方式3:Kotlin 懒汉式(线程不安全示例)

class LazySingletonUnsafe private constructor() {
    companion object {
        private var instance: LazySingletonUnsafe? = null

        fun getInstance(): LazySingletonUnsafe {
            if (instance == null) {
                instance = LazySingletonUnsafe()
            }
            return instance!!
        }
    }
}

解读

  • 首次调用才创建实例(懒加载)。
  • 多线程下可能创建多个实例,仅用于理解概念,不建议生产使用。

方式4:Kotlin 线程安全懒汉式(推荐)

class LazySingletonSafe private constructor() {
    companion object {
        val instance: LazySingletonSafe by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
            LazySingletonSafe()
        }
    }
}

解读

  • 首次访问才创建,且线程安全。
  • by lazy 是 Kotlin 中实现懒单例最实用写法。

方式5:Kotlin DCL 双重检查锁(进阶)

class DclSingleton private constructor() {
    companion object {
        @Volatile
        private var instance: DclSingleton? = null

        fun getInstance(): DclSingleton {
            return instance ?: synchronized(this) {
                instance ?: DclSingleton().also { instance = it }
            }
        }
    }
}

解读

  • 线程安全 + 懒加载 + 减少无效加锁。
  • 需要 @Volatile 保证可见性。

单例“状态与生命周期”如何体现

object RequestStats {
    var total = 0
    fun inc() { total++ }
}
  • 状态total 会持续变化并被保留。
  • 生命周期:通常首次访问初始化,进程结束才释放。
  • 风险:全局可变状态容易带来并发问题和测试污染。

Kotlin 单例选型建议

  • 默认场景object
  • 需要惰性创建by lazy
  • 与旧写法兼容或性能细调:DCL
  • 中大型项目:建议结合 DI(如 Koin/Hilt)管理作用域单例

相似概念区分:单例 vs 静态工具类

  • 单例:有实例、可持有状态、有生命周期
  • 静态工具类:通常无状态,只是函数集合

单例常见坑

  • 把业务可变状态都堆进单例,导致隐式耦合
  • 忽略线程安全(计数器、集合写入)
  • 直接依赖外部 IO,导致测试困难
  • 把单例当“全局共享垃圾桶”

2.2 工厂方法(Factory Method)

解决问题:调用方不希望依赖具体实现,且创建逻辑可能变化。
核心思路:把“创建哪种对象”交给具体工厂子类。

interface Logger {
    fun log(msg: String)
}

class ConsoleLogger : Logger {
    override fun log(msg: String) = println("Console: $msg")
}

abstract class LoggerFactory {
    abstract fun create(): Logger
}

class ConsoleLoggerFactory : LoggerFactory() {
    override fun create(): Logger = ConsoleLogger()
}

fun main() {
    val logger = ConsoleLoggerFactory().create()
    logger.log("hello")
}

代码解读

  • 调用方只依赖 LoggerLoggerFactory 抽象。
  • 未来新增 FileLoggerFactory 不影响现有调用代码。

相似模式区分

  • 对比抽象工厂:工厂方法通常关注“一个产品层级”。

何时不用

  • 只有一个实现且长期稳定时,直接构造更简单。

2.3 抽象工厂(Abstract Factory)

解决问题:要创建“一整套相互匹配对象”(产品族)。
核心思路:一个工厂产出多个相关产品,保证风格一致。

interface Button { fun render() }
interface Checkbox { fun render() }

class WinButton : Button { override fun render() = println("WinButton") }
class WinCheckbox : Checkbox { override fun render() = println("WinCheckbox") }

interface UIFactory {
    fun createButton(): Button
    fun createCheckbox(): Checkbox
}

class WinFactory : UIFactory {
    override fun createButton() = WinButton()
    override fun createCheckbox() = WinCheckbox()
}

fun main() {
    val factory: UIFactory = WinFactory()
    factory.createButton().render()
    factory.createCheckbox().render()
}

代码解读

  • UIFactory 一次定义多个“产品创建方法”。
  • WinFactory 保证按钮和复选框同属 Win 产品族。

相似模式区分

  • 工厂方法:一个产品。
  • 抽象工厂:一组关联产品。

2.4 建造者(Builder)

解决问题:复杂对象构造参数多、校验复杂。
核心思路:分步骤设置参数,最后 build()

data class User(val id: Long, val name: String, val email: String?)

class UserBuilder {
    private var id: Long = 0
    private var name: String = ""
    private var email: String? = null

    fun id(v: Long) = apply { id = v }
    fun name(v: String) = apply { name = v }
    fun email(v: String?) = apply { email = v }

    fun build(): User {
        require(name.isNotBlank()) { "name 不能为空" }
        return User(id, name, email)
    }
}

fun main() {
    val user = UserBuilder().id(1).name("Alice").email("a@test.com").build()
    println(user)
}

代码解读

  • apply 让链式调用简洁。
  • build 集中做完整性校验。

相似模式区分

  • 和抽象工厂不同:Builder 关注“构建过程”,不是产品族。

2.5 原型(Prototype)

解决问题:对象创建昂贵,想通过复制快速生成。
核心思路:以现有对象为模板,拷贝得到新对象。

data class Report(val title: String, val tags: MutableList<String>) {
    fun deepCopy(): Report = Report(title, tags.toMutableList())
}

fun main() {
    val r1 = Report("Q1", mutableListOf("finance"))
    val r2 = r1.deepCopy()
    r2.tags += "draft"
    println(r1.tags) // [finance]
    println(r2.tags) // [finance, draft]
}

代码解读

  • 关键在“深拷贝”,避免共享可变引用导致互相污染。

相似模式区分

  • Builder 是“从零组装”;Prototype 是“基于现成对象复制”。

3. 结构型模式(7)

本类模式特点速记(结构型)

模式核心特点(详细)代码识别信号记忆点
适配器不改原系统前提下做接口转换,常用于旧系统兼容与第三方接入新接口实现类内部调用旧接口接口不合就转接
桥接抽象与实现拆分,解决多维变化导致的继承爆炸抽象类持有实现接口引用两条维度两座桥
组合树形结构统一处理整体与部分;递归结构天然适配叶子与容器同接口叶子容器一把尺子
装饰器运行时动态叠加能力,不改原类;可多层组合Decorator 持有同接口对象不改原类加功能
外观对复杂子系统提供统一入口,降低调用复杂度Facade 聚合多个子系统对象对外一个门面
享元抽离可共享内部状态,减少海量对象内存占用工厂缓存共享对象能共享就别重复造
代理控制访问(懒加载、鉴权、远程、缓存)而非增强业务能力代理与真实对象同接口先把关再放行

3.1 适配器(Adapter)

解决问题:已有类接口不符合当前系统要求。
核心思路:包一层转换,旧接口“适配”为新接口。

interface Payment { fun pay(amount: Double) }

class LegacyPayService {
    fun makePayment(money: Double) = println("legacy pay: $money")
}

class LegacyPayAdapter(private val legacy: LegacyPayService) : Payment {
    override fun pay(amount: Double) = legacy.makePayment(amount)
}

代码解读

  • 调用方只认 Payment
  • 旧系统调用细节被 LegacyPayAdapter 隔离。

相似模式区分

  • 和代理不同:适配器是“接口转换”,代理是“访问控制”。

何时不用

  • 能直接修改源接口或统一协议时,不必额外加适配层。

3.2 桥接(Bridge)

解决问题:一个类有两个维度都在变化,继承会爆炸。
核心思路:抽象与实现分离,组合替代继承。

interface Device {
    fun on()
    fun off()
}

class Tv : Device {
    override fun on() = println("TV on")
    override fun off() = println("TV off")
}

open class Remote(protected val device: Device) {
    fun turnOn() = device.on()
    fun turnOff() = device.off()
}

class AdvancedRemote(device: Device) : Remote(device) {
    fun mute() = println("mute")
}

代码解读

  • Remote 是抽象维度,Device 是实现维度。
  • 两个维度可以独立扩展。

相似模式区分

  • 适配器是“后期补兼容”,桥接是“前期做解耦”。

3.3 组合(Composite)

解决问题:树形结构中,希望“单个对象”和“组合对象”统一处理。
核心思路:叶子与容器实现同一接口,递归处理。

interface Node {
    fun show(indent: String = "")
}

class FileNode(private val name: String) : Node {
    override fun show(indent: String) = println("$indent- $name")
}

class FolderNode(private val name: String) : Node {
    private val children = mutableListOf<Node>()
    fun add(node: Node) = children.add(node)
    override fun show(indent: String) {
        println("$indent+ $name")
        children.forEach { it.show("$indent  ") }
    }
}

代码解读

  • FolderNode 里面可放 FileNode 也可放 FolderNode
  • 通过同一 Node 接口实现统一操作。

相似模式区分

  • 组合关注“树结构表达”;装饰器关注“能力叠加”。

3.4 装饰器(Decorator)

解决问题:不改原类前提下,动态增加能力。
核心思路:包装对象,在调用前后附加逻辑。

interface Notifier {
    fun send(msg: String)
}

class EmailNotifier : Notifier {
    override fun send(msg: String) = println("Email: $msg")
}

open class NotifierDecorator(private val wrappee: Notifier) : Notifier {
    override fun send(msg: String) = wrappee.send(msg)
}

class SmsDecorator(wrappee: Notifier) : NotifierDecorator(wrappee) {
    override fun send(msg: String) {
        super.send(msg)
        println("SMS: $msg")
    }
}

代码解读

  • SmsDecorator 保留原有行为,再追加新行为。
  • 多个装饰器可叠加使用。

相似模式区分

  • 装饰器是增强功能;代理是控制访问。

3.5 外观(Facade)

解决问题:子系统复杂,调用方学习成本高。
核心思路:封装复杂流程,对外暴露统一入口。

class Cpu { fun run() = println("CPU run") }
class Memory { fun load() = println("Memory load") }
class Disk { fun read() = println("Disk read") }

class ComputerFacade(
    private val cpu: Cpu = Cpu(),
    private val memory: Memory = Memory(),
    private val disk: Disk = Disk()
) {
    fun start() {
        disk.read()
        memory.load()
        cpu.run()
    }
}

代码解读

  • 调用方只调用 start(),不关心内部细节。

相似模式区分

  • 外观面向“客户端简化”;中介者面向“对象协作解耦”。

3.6 享元(Flyweight)

解决问题:大量相似对象导致内存浪费。
核心思路:抽离并共享可复用状态。

class TreeType(val name: String)

object TreeTypeFactory {
    private val cache = mutableMapOf<String, TreeType>()
    fun get(name: String): TreeType = cache.getOrPut(name) { TreeType(name) }
}

data class Tree(val x: Int, val y: Int, val type: TreeType)

代码解读

  • TreeType 可共享(内部状态)。
  • x/y 由单个对象持有(外部状态)。

相似模式区分

  • 享元核心是“共享复用减少内存”,不是功能增强。

3.7 代理(Proxy)

解决问题:需要控制对象访问(懒加载、权限、缓存、远程)。
核心思路:代理对象与真实对象同接口,拦截并转发。

interface Image { fun display() }

class RealImage(private val file: String) : Image {
    init { println("Loading $file") }
    override fun display() = println("Display $file")
}

class ImageProxy(private val file: String) : Image {
    private var real: RealImage? = null
    override fun display() {
        if (real == null) real = RealImage(file)
        real!!.display()
    }
}

代码解读

  • 首次调用时才创建真实对象,实现延迟加载。

相似模式区分

  • 装饰器偏“加功能”;代理偏“控访问”。

4. 行为型模式(11)

本类模式特点速记(行为型)

模式核心特点(详细)代码识别信号记忆点
责任链请求沿链传递,节点可插拔;解耦发送与处理next 指针 + process/handle一节一责,不能就传
命令把动作对象化,支持排队、日志、撤销重做Command.execute()请求即对象
解释器用对象表达语法并递归执行;适合简单 DSLExpr.interpret(ctx)规则即语法树
迭代器统一遍历入口,屏蔽集合内部结构iterator()只管遍历不管内部
中介者将网状调用收束到中介,减少对象间直接依赖Mediator.send(...)多方沟通找中台
备忘录保存与恢复快照,不破坏封装save()/restore()先存档再回滚
观察者一对多通知机制,发布者与订阅者解耦subscribe/notify一处变化,多处感知
状态状态对象驱动行为与迁移,替代巨型分支ctx.state = XxxState()状态变,行为变
策略同一任务多算法,运行时替换setStrategy/构造注入策略同入口,换算法
模板方法父类固定流程骨架,子类实现可变步骤final流程 + abstract步骤骨架固定,步骤可变
访问者元素结构稳定时新增操作更方便accept(visitor) + visit结构不动,加操作

4.1 责任链(Chain of Responsibility)

解决问题:多个处理规则,避免巨型 if-else。
核心思路:请求沿链传递,谁能处理谁处理。

abstract class Handler(private val next: Handler? = null) {
    fun handle(level: Int) {
        if (!process(level)) next?.handle(level)
    }
    protected abstract fun process(level: Int): Boolean
}

class InfoHandler(next: Handler? = null) : Handler(next) {
    override fun process(level: Int): Boolean {
        if (level == 1) { println("Info handled"); return true }
        return false
    }
}

class ErrorHandler(next: Handler? = null) : Handler(next) {
    override fun process(level: Int): Boolean {
        if (level == 2) { println("Error handled"); return true }
        return false
    }
}

代码解读

  • 每个节点只负责自己逻辑,不关心链全貌。

相似模式区分

  • 和命令不同:责任链关注“谁来处理”,命令关注“把请求对象化”。

4.2 命令(Command)

解决问题:操作需要记录、排队、撤销。
核心思路:把一次操作封装成命令对象。

interface Command { fun execute() }

class Light { fun on() = println("Light on") }

class LightOnCommand(private val light: Light) : Command {
    override fun execute() = light.on()
}

class Remote(private var command: Command) {
    fun setCommand(command: Command) { this.command = command }
    fun press() = command.execute()
}

代码解读

  • 调用方只触发 command.execute(),与具体接收者解耦。

相似模式区分

  • 命令偏“操作封装与调度”;责任链偏“请求传递路径”。

4.3 解释器(Interpreter)

解决问题:需要实现小型表达式语言。
核心思路:把语法规则映射为对象结构并解释执行。

interface Expr { fun interpret(ctx: Map<String, Int>): Int }

class Num(private val value: Int) : Expr {
    override fun interpret(ctx: Map<String, Int>) = value
}

class Var(private val name: String) : Expr {
    override fun interpret(ctx: Map<String, Int>) = ctx[name] ?: 0
}

class Add(private val left: Expr, private val right: Expr) : Expr {
    override fun interpret(ctx: Map<String, Int>) = left.interpret(ctx) + right.interpret(ctx)
}

代码解读

  • Add(Var("x"), Num(3)) 直接表达了语法树结构。

相似模式区分

  • 解释器是“语法规则执行”,不是简单策略切换。

4.4 迭代器(Iterator)

解决问题:遍历集合但不暴露内部结构。
核心思路:提供统一迭代访问接口。

class MyList<T>(private val items: List<T>) : Iterable<T> {
    override fun iterator(): Iterator<T> = items.iterator()
}

代码解读

  • 调用方只需 for (x in myList),无需关心底层容器结构。

相似模式区分

  • 迭代器关注“访问方式抽象”,不负责业务处理逻辑。

4.5 中介者(Mediator)

解决问题:对象之间互相依赖过多,关系网复杂。
核心思路:让对象都通过中介通信。

interface ChatMediator { fun send(msg: String, user: User) }

class ChatRoom : ChatMediator {
    private val users = mutableListOf<User>()
    fun add(user: User) { users += user }
    override fun send(msg: String, user: User) {
        users.filter { it != user }.forEach { it.receive(msg) }
    }
}

class User(private val name: String, private val mediator: ChatMediator) {
    fun send(msg: String) = mediator.send("$name: $msg", this)
    fun receive(msg: String) = println("[$name] $msg")
}

代码解读

  • User 不直接持有其他用户引用,降低耦合。

相似模式区分

  • 外观简化“对外调用”;中介者简化“内部交互”。

4.6 备忘录(Memento)

解决问题:需要保存并恢复历史状态。
核心思路:状态快照对象 + 恢复机制。

data class EditorMemento(val content: String)

class Editor {
    var content: String = ""
    fun save(): EditorMemento = EditorMemento(content)
    fun restore(m: EditorMemento) { content = m.content }
}

代码解读

  • save 记录快照,restore 回滚状态。

相似模式区分

  • 和命令常组合使用:命令负责动作,备忘录负责状态回滚。

4.7 观察者(Observer)

解决问题:一个对象变化,需要自动通知多个对象。
核心思路:发布者维护订阅者列表,事件发生时广播。

interface Observer { fun update(data: String) }

class Subject {
    private val observers = mutableListOf<Observer>()
    fun subscribe(o: Observer) = observers.add(o)
    fun notifyAllObservers(data: String) = observers.forEach { it.update(data) }
}

class AppObserver(private val name: String) : Observer {
    override fun update(data: String) = println("$name got: $data")
}

代码解读

  • 发布者不关心订阅者具体实现,只依赖 Observer 接口。

相似模式区分

  • 观察者是“一对多通知”;中介者是“多对多协调”。

4.8 状态(State)

解决问题:对象行为随内部状态变化,分支判断越来越多。
核心思路:把状态抽成类,由状态对象决定行为与流转。

interface OrderState { fun next(ctx: OrderContext) }

class Created : OrderState {
    override fun next(ctx: OrderContext) {
        println("Created -> Paid")
        ctx.state = Paid()
    }
}

class Paid : OrderState {
    override fun next(ctx: OrderContext) {
        println("Paid -> Shipped")
        ctx.state = Shipped()
    }
}

class Shipped : OrderState {
    override fun next(ctx: OrderContext) = println("Already shipped")
}

class OrderContext(var state: OrderState) {
    fun next() = state.next(this)
}

代码解读

  • 行为从 when(state) 转为状态类分发,扩展更平滑。

相似模式区分

  • 状态是“对象自己随状态变化”;策略是“外部选择算法”。

何时不用

  • 状态很少且变化极低时,enum + when 往往更直观。

4.9 策略(Strategy)

解决问题:同一任务有多种算法实现,需要可替换。
核心思路:把算法封装成策略,运行时注入。

interface DiscountStrategy { fun apply(price: Double): Double }

class NoDiscount : DiscountStrategy {
    override fun apply(price: Double) = price
}

class VipDiscount : DiscountStrategy {
    override fun apply(price: Double) = price * 0.8
}

class PriceService(private var strategy: DiscountStrategy) {
    fun setStrategy(strategy: DiscountStrategy) { this.strategy = strategy }
    fun calc(price: Double): Double = strategy.apply(price)
}

代码解读

  • PriceService 不知道具体折扣算法,只依赖策略接口。

相似模式区分

  • 模板方法通过继承改步骤;策略通过组合换算法。

何时不用

  • 算法唯一且短期不会变化时,直接实现更轻量。

4.10 模板方法(Template Method)

解决问题:流程骨架固定,但某些步骤可定制。
核心思路:父类定义流程,子类实现变化点。

abstract class DataParser {
    fun parse() {
        read()
        process()
        save()
    }
    protected abstract fun read()
    protected abstract fun process()
    protected open fun save() = println("save result")
}

class CsvParser : DataParser() {
    override fun read() = println("read csv")
    override fun process() = println("process csv")
}

代码解读

  • parse() 是不可变骨架,read/process 是可变步骤。

相似模式区分

  • 模板方法偏“继承复用”;策略偏“组合替换”。

4.11 访问者(Visitor)

解决问题:数据结构稳定,但操作经常新增。
核心思路:把操作抽离成访问者,元素接受访问。

interface Visitor {
    fun visit(file: FileNode)
    fun visit(folder: FolderNode)
}

interface Element {
    fun accept(visitor: Visitor)
}

class FileNode(val name: String) : Element {
    override fun accept(visitor: Visitor) = visitor.visit(this)
}

class FolderNode(val name: String, val children: List<Element>) : Element {
    override fun accept(visitor: Visitor) = visitor.visit(this)
}

class PrintVisitor : Visitor {
    override fun visit(file: FileNode) = println("File: ${file.name}")
    override fun visit(folder: FolderNode) {
        println("Folder: ${folder.name}")
        folder.children.forEach { it.accept(this) }
    }
}

代码解读

  • 新增操作时添加新 Visitor,不改元素类。

相似模式区分

  • 若元素结构频繁变化,不适合访问者(改动面会很大)。

5. 相似模式对比与关系总表(高频易混 + 协作边界)

模式A模式B核心区别典型协作/选择建议
工厂方法抽象工厂工厂方法创建一个产品层级;抽象工厂创建一组产品族先判断是否需要“整套一致性”
抽象工厂建造者抽象工厂关注“产什么”;建造者关注“怎么一步步构建”产品族切换用抽象工厂,复杂参数构建用建造者
策略状态策略是外部选算法;状态是内部状态驱动行为算法切换频繁用策略,流程流转复杂用状态
装饰器代理装饰器增强能力;代理控制访问同时需要增强+控制时可叠加使用
装饰器适配器装饰器不改接口;适配器改接口先兼容接口(适配器),再按需增强(装饰器)
模板方法策略模板方法基于继承;策略基于组合运行期可切换优先策略,流程固定优先模板方法
外观中介者外观简化对外调用;中介者协调内部交互先对内解耦可用中介者,再对外收敛用外观
桥接适配器桥接是事前解耦多维变化;适配器是事后兼容新系统做桥接,旧系统接入做适配
观察者中介者观察者是一对多通知;中介者是多方协调事件广播用观察者,复杂联动用中介者
责任链命令责任链决定谁处理;命令封装做什么可先命令化请求,再用责任链分发

6. Kotlin 语境下的实战建议

  • 单例(Singleton):默认优先 object;当你需要明确初始化时机或自定义线程策略,再考虑 by lazy / DCL。
  • 策略(Strategy):算法差异小且无状态时,可先用高阶函数;算法复杂、依赖多、需单测隔离时升级为策略接口。
  • 状态(State):先看是否存在“状态迁移图”;若只是少量分支,enum + when 更轻,避免类数量膨胀。
  • 装饰器(Decorator):用于“同接口增强能力”;如果目的是权限、缓存、远程访问控制,更应选代理(Proxy)。
  • 原型(Prototype)copy 适合不可变浅拷贝;涉及嵌套可变对象时,必须显式深拷贝。
  • 工厂方法 / 抽象工厂:先判断需求是“一个产品”还是“一整套产品族”,避免过度设计。
  • 责任链(Chain of Responsibility):规则需要插拔且顺序可调时再用;固定短流程直接顺序代码即可。

7. 何时不要用:23 模式反例速查

  • Singleton:业务状态频繁变化且需要多实例隔离时不要用。
  • Factory Method:只有一个实现且长期不变时不要用。
  • Abstract Factory:不存在“产品族一致性”要求时不要用。
  • Builder:参数很少且默认参数已足够时不要用。
  • Prototype:对象结构简单、复制收益很低时不要用。
  • Adapter:可以直接改源接口时,不必额外适配层。
  • Bridge:没有多维变化时,不要为了“架构好看”而拆。
  • Composite:非树形结构不要硬套递归组合。
  • Decorator:增强逻辑固定且不需要组合时可直接实现。
  • Facade:不要把业务编排都塞进 Facade 变成上帝类。
  • Flyweight:对象量不大时,收益小于复杂度。
  • Proxy:仅做功能增强时,优先装饰器。
  • Chain:规则强依赖固定顺序且很短时可直接顺序调用。
  • Command:不需要排队/撤销/记录时,不必对象化请求。
  • Interpreter:复杂语法不要手写解释器,优先成熟解析工具。
  • Iterator:语言内置遍历已满足时,不要重复封装。
  • Mediator:交互简单时,引入中介反而增加跳转成本。
  • Memento:状态很大且无压缩策略时,快照成本过高。
  • Observer:严格同步链路且有强事务一致性要求时要谨慎。
  • State:状态非常少且稳定时,enum + when 更直接。
  • Strategy:算法只有一种且不会变化时,不要过抽象。
  • Template Method:变化点很多时,继承会脆弱,改用策略组合。
  • Visitor:元素结构频繁变动时,Visitor 维护成本很高。

8. 模式选择决策表(实战最常用)

你看到的坏味道优先考虑模式备选模式选择提示
大量 if-else 选择算法StrategyState / Chain外部切换用 Strategy,内部流转用 State
订单流程分支越来越多StateStrategy与“状态迁移”绑定就用 State
一条请求要多层校验ChainCommand可插拔规则链优先 Chain
新旧 SDK 接口不兼容AdapterFacade仅改接口形状用 Adapter
调用子系统过于复杂FacadeMediator对外简化入口优先 Facade
需要动态叠加能力DecoratorProxy增强功能用 Decorator,控制访问用 Proxy
对象创建逻辑分散Factory MethodAbstract Factory / Builder单产品用工厂,产品族用抽象工厂
构造参数太多太乱BuilderFactory Method有步骤/校验要求时用 Builder

9. Kotlin 工程化补充(协程 / Flow / DI)

  • 响应式建模StateFlow 建模持续状态,SharedFlow 建模一次性事件,减少手写观察者样板与生命周期问题。
  • 依赖注入与作用域:使用 Hilt/Koin 管理对象生命周期(Application / Activity / Feature),把“单例”从全局变量升级为可管理作用域。
  • 协程任务治理:将命令/任务抽象为 suspend,统一超时、取消、重试、失败上报与审计日志。
  • 可测试性优先:模式实现尽量依赖接口 + 注入,避免直接硬编码全局对象,便于 mock 与替换。
  • 样板代码控制:在装饰器或代理链路中使用 Kotlin 委托 by,减少重复转发代码并提高可读性。

10. 面试高频问答(18 题)

  1. 策略(Strategy)和状态(State)的本质区别是什么?
    二者结构很像,都是“上下文 + 接口 + 多实现类”。真正区别在“切换来源”:策略通常由外部(配置、用户类型、AB 实验)决定,目的是替换算法;状态由对象内部生命周期决定,目的是驱动行为流转。判断口诀是:外部选算法用策略,内部走流程用状态

  2. 装饰器(Decorator)和代理(Proxy)都包一层,如何区分?
    装饰器的重点是“增强能力”,比如发送通知时顺带加短信、埋点、审计;代理的重点是“控制访问”,比如权限校验、远程调用、缓存、懒加载。二者都实现同一接口,但意图不同:装饰器加料,代理把关

  3. 工厂方法(Factory Method)和抽象工厂(Abstract Factory)怎么选?
    如果你只需要创建一个产品层级中的对象(如 Logger),用工厂方法;如果你需要创建一整套必须协同的产品族(如 Button + Checkbox 同主题),用抽象工厂。简单说:一个产品用工厂方法,一组产品用抽象工厂

  4. 模板方法(Template Method)和策略(Strategy)怎么选?
    模板方法基于继承,父类固定流程骨架,子类覆盖步骤;策略基于组合,运行时替换算法实现。流程稳定、步骤可变时模板方法更清晰;需要频繁切换算法或动态组合时策略更灵活。

  5. 为什么状态模式可以消除巨型 if-else
    因为把“状态分支”拆进各状态类,每个状态只关心自己的行为与迁移。新增状态时通常新增类而非修改中心分支,减少回归风险,更符合开闭原则。

  6. 责任链(Chain of Responsibility)的优缺点是什么?
    优点:处理器可插拔、顺序可调整、发送方与处理方解耦。缺点:链太长会难追踪,且若缺少“兜底处理”可能出现请求无人处理。工程上要配链路日志和终止条件。

  7. 观察者(Observer)在工程中的典型风险有哪些?
    常见风险有通知风暴、循环触发、时序不一致、异常传播不清晰。落地时要约定线程模型、是否同步、失败重试策略,并防止订阅未释放导致内存泄漏。

  8. 访问者(Visitor)为什么说“对操作开放、对结构不友好”?
    Visitor 适合“元素结构稳定、操作经常新增”的场景;新增操作只要加新 Visitor,不改元素类。但一旦元素结构变化,所有 Visitor 都可能要改,维护成本会迅速上升。

  9. 享元(Flyweight)什么时候收益最大?
    在“对象数量巨大 + 存在大量可共享内部状态”时收益明显,比如字体字形、地图元素、棋子模型。若对象规模不大,享元增加的复杂度可能得不偿失。

  10. Kotlin 中单例最佳实践是什么?
    默认优先 object,简单、线程安全、语义明确;需延迟初始化时可用 by lazy;中大型项目推荐交给 DI 容器管理作用域单例。单例中尽量避免大规模可变业务状态。

  11. Facade(外观)和 Mediator(中介者)为什么易混?
    都是“集中化”思路,但方向不同:Facade 面向客户端,目标是简化子系统调用入口;Mediator 面向内部对象,目标是消除对象之间网状依赖。一个对外简化,一个对内解耦。

  12. 设计模式是不是用得越多越好?
    不是。模式是为控制复杂度服务,不是展示技巧。正确策略是先用简单实现,复杂度真实出现后再引入最小必要模式,避免过度抽象。

  13. 适配器(Adapter)和桥接(Bridge)有什么本质不同?
    Adapter 是“事后兼容”,解决现有不兼容接口协作问题;Bridge 是“事前解耦”,为避免多维变化导致类爆炸而预先拆分抽象与实现。前者偏补救,后者偏设计。

  14. 命令模式(Command)在实际项目中最常见的价值是什么?
    把操作对象化后,可以统一排队、重试、记录、回放、撤销,特别适合任务调度、审计日志、操作历史。它让“调用”变成可管理的数据单元。

  15. 备忘录(Memento)和命令(Command)能一起用吗?
    很常见。命令记录“做了什么”,备忘录保存“改前状态”。实现撤销时可先用命令回放,也可直接用备忘录回滚,二者结合可以兼顾语义清晰与恢复效率。

  16. 何时不该使用状态模式?
    当状态很少且几乎不变时,enum + when 通常更直接。若强行引入状态类会增加样板代码。状态模式更适合状态多、迁移规则复杂、变更频繁的业务流程。

  17. 如何判断当前代码是否该上策略模式?
    看到“同一业务目标 + 多种可替换算法 + 外部条件选择”就是策略信号。典型坏味道是巨大 if-else / when 在做算法分派,且分支还在持续增加。

  18. 面试里如何回答“你在项目中怎么落地设计模式”?
    建议用三段式:

    • 问题:原代码坏味道(分支爆炸、耦合重、难扩展)
    • 方案:引入哪个模式、为什么不是别的模式
    • 结果:新增需求改动面、回归风险、测试覆盖、迭代效率的量化变化
      这种回答比只讲定义更有说服力。