1. GoF 设计模式总览
GoF(Gang of Four)提出了 23 种经典设计模式,分三类:
- 创建型(5):对象怎么创建更灵活
- 结构型(7):类和对象怎么组合更合理
- 行为型(11):对象之间怎么协作更清晰
1.1 23 模式脑图(文字版)
- 创建型:
Singleton、Factory Method、Abstract Factory、Builder、Prototype - 结构型:
Adapter、Bridge、Composite、Decorator、Facade、Flyweight、Proxy - 行为型:
Chain、Command、Interpreter、Iterator、Mediator、Memento、Observer、State、Strategy、Template Method、Visitor
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")
}
代码解读:
- 调用方只依赖
Logger和LoggerFactory抽象。 - 未来新增
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() | 请求即对象 |
| 解释器 | 用对象表达语法并递归执行;适合简单 DSL | Expr.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 选择算法 | Strategy | State / Chain | 外部切换用 Strategy,内部流转用 State |
| 订单流程分支越来越多 | State | Strategy | 与“状态迁移”绑定就用 State |
| 一条请求要多层校验 | Chain | Command | 可插拔规则链优先 Chain |
| 新旧 SDK 接口不兼容 | Adapter | Facade | 仅改接口形状用 Adapter |
| 调用子系统过于复杂 | Facade | Mediator | 对外简化入口优先 Facade |
| 需要动态叠加能力 | Decorator | Proxy | 增强功能用 Decorator,控制访问用 Proxy |
| 对象创建逻辑分散 | Factory Method | Abstract Factory / Builder | 单产品用工厂,产品族用抽象工厂 |
| 构造参数太多太乱 | Builder | Factory Method | 有步骤/校验要求时用 Builder |
9. Kotlin 工程化补充(协程 / Flow / DI)
- 响应式建模:
StateFlow建模持续状态,SharedFlow建模一次性事件,减少手写观察者样板与生命周期问题。 - 依赖注入与作用域:使用 Hilt/Koin 管理对象生命周期(Application / Activity / Feature),把“单例”从全局变量升级为可管理作用域。
- 协程任务治理:将命令/任务抽象为
suspend,统一超时、取消、重试、失败上报与审计日志。 - 可测试性优先:模式实现尽量依赖接口 + 注入,避免直接硬编码全局对象,便于 mock 与替换。
- 样板代码控制:在装饰器或代理链路中使用 Kotlin 委托
by,减少重复转发代码并提高可读性。
10. 面试高频问答(18 题)
-
策略(Strategy)和状态(State)的本质区别是什么?
二者结构很像,都是“上下文 + 接口 + 多实现类”。真正区别在“切换来源”:策略通常由外部(配置、用户类型、AB 实验)决定,目的是替换算法;状态由对象内部生命周期决定,目的是驱动行为流转。判断口诀是:外部选算法用策略,内部走流程用状态。 -
装饰器(Decorator)和代理(Proxy)都包一层,如何区分?
装饰器的重点是“增强能力”,比如发送通知时顺带加短信、埋点、审计;代理的重点是“控制访问”,比如权限校验、远程调用、缓存、懒加载。二者都实现同一接口,但意图不同:装饰器加料,代理把关。 -
工厂方法(Factory Method)和抽象工厂(Abstract Factory)怎么选?
如果你只需要创建一个产品层级中的对象(如Logger),用工厂方法;如果你需要创建一整套必须协同的产品族(如Button + Checkbox同主题),用抽象工厂。简单说:一个产品用工厂方法,一组产品用抽象工厂。 -
模板方法(Template Method)和策略(Strategy)怎么选?
模板方法基于继承,父类固定流程骨架,子类覆盖步骤;策略基于组合,运行时替换算法实现。流程稳定、步骤可变时模板方法更清晰;需要频繁切换算法或动态组合时策略更灵活。 -
为什么状态模式可以消除巨型
if-else?
因为把“状态分支”拆进各状态类,每个状态只关心自己的行为与迁移。新增状态时通常新增类而非修改中心分支,减少回归风险,更符合开闭原则。 -
责任链(Chain of Responsibility)的优缺点是什么?
优点:处理器可插拔、顺序可调整、发送方与处理方解耦。缺点:链太长会难追踪,且若缺少“兜底处理”可能出现请求无人处理。工程上要配链路日志和终止条件。 -
观察者(Observer)在工程中的典型风险有哪些?
常见风险有通知风暴、循环触发、时序不一致、异常传播不清晰。落地时要约定线程模型、是否同步、失败重试策略,并防止订阅未释放导致内存泄漏。 -
访问者(Visitor)为什么说“对操作开放、对结构不友好”?
Visitor 适合“元素结构稳定、操作经常新增”的场景;新增操作只要加新 Visitor,不改元素类。但一旦元素结构变化,所有 Visitor 都可能要改,维护成本会迅速上升。 -
享元(Flyweight)什么时候收益最大?
在“对象数量巨大 + 存在大量可共享内部状态”时收益明显,比如字体字形、地图元素、棋子模型。若对象规模不大,享元增加的复杂度可能得不偿失。 -
Kotlin 中单例最佳实践是什么?
默认优先object,简单、线程安全、语义明确;需延迟初始化时可用by lazy;中大型项目推荐交给 DI 容器管理作用域单例。单例中尽量避免大规模可变业务状态。 -
Facade(外观)和 Mediator(中介者)为什么易混?
都是“集中化”思路,但方向不同:Facade 面向客户端,目标是简化子系统调用入口;Mediator 面向内部对象,目标是消除对象之间网状依赖。一个对外简化,一个对内解耦。 -
设计模式是不是用得越多越好?
不是。模式是为控制复杂度服务,不是展示技巧。正确策略是先用简单实现,复杂度真实出现后再引入最小必要模式,避免过度抽象。 -
适配器(Adapter)和桥接(Bridge)有什么本质不同?
Adapter 是“事后兼容”,解决现有不兼容接口协作问题;Bridge 是“事前解耦”,为避免多维变化导致类爆炸而预先拆分抽象与实现。前者偏补救,后者偏设计。 -
命令模式(Command)在实际项目中最常见的价值是什么?
把操作对象化后,可以统一排队、重试、记录、回放、撤销,特别适合任务调度、审计日志、操作历史。它让“调用”变成可管理的数据单元。 -
备忘录(Memento)和命令(Command)能一起用吗?
很常见。命令记录“做了什么”,备忘录保存“改前状态”。实现撤销时可先用命令回放,也可直接用备忘录回滚,二者结合可以兼顾语义清晰与恢复效率。 -
何时不该使用状态模式?
当状态很少且几乎不变时,enum + when通常更直接。若强行引入状态类会增加样板代码。状态模式更适合状态多、迁移规则复杂、变更频繁的业务流程。 -
如何判断当前代码是否该上策略模式?
看到“同一业务目标 + 多种可替换算法 + 外部条件选择”就是策略信号。典型坏味道是巨大if-else/when在做算法分派,且分支还在持续增加。 -
面试里如何回答“你在项目中怎么落地设计模式”?
建议用三段式:- 问题:原代码坏味道(分支爆炸、耦合重、难扩展)
- 方案:引入哪个模式、为什么不是别的模式
- 结果:新增需求改动面、回归风险、测试覆盖、迭代效率的量化变化
这种回答比只讲定义更有说服力。