1. 引言

设计模式在构建健壮、可维护且可扩展的代码中起着至关重要的作用。其中,代理模式(Proxy Pattern) 因其灵活性和实用性脱颖而出。

本文将深入探讨代理模式的定义、典型应用场景以及在 Kotlin 中的具体实现方式。对于有经验的开发者而言,掌握这一模式有助于解耦核心逻辑与横切关注点(如权限控制、日志、延迟加载等),是日常开发中的“利器”。


2. 理解代理模式

代理模式是一种结构型设计模式,它为某个对象提供一个代理(或占位符),以控制对该对象的访问

这个代理可以在真实对象的方法调用前后插入额外行为,比如:

  • 延迟初始化(Lazy Initialization)
  • 权限校验
  • 日志记录
  • 远程通信封装

关键在于:代理和真实对象实现同一接口,客户端无需感知它们之间的差异,从而实现了透明访问。

💡 核心思想:用代理层隔离直接依赖,增强控制力而不侵入原逻辑。


3. 代理模式的常见变体

以下是几种典型的代理模式应用场景,每种都对应不同的业务需求。

3.1. 虚拟代理(Virtual Proxy)

用于延迟创建开销较大的对象,直到真正需要时才实例化,常用于资源密集型操作。

interface RealObject {
    fun performOperation()
}

class RealObjectImpl : RealObject {
    override fun performOperation() {
        println("RealObject performing operation")
    }
}

class VirtualProxy : RealObject {
    private val realObject by lazy { RealObjectImpl() }
    override fun performOperation() {
        realObject.performOperation()
    }
}

📌 踩坑提醒:lazy { } 默认是线程安全的(LazyThreadSafetyMode.SYNCHRONIZED),如果性能敏感且确定单线程使用,可显式指定 lazy(LazyThreadSafetyMode.NONE) { } 提升效率。

✅ 优势:避免启动阶段不必要的资源消耗。


3.2. 保护代理(Protection Proxy)

控制对敏感对象的访问权限,确保只有授权用户才能执行操作,适用于基于角色的权限系统。

interface SensitiveObject {
    fun access()
}

class SensitiveObjectImpl : SensitiveObject {
    override fun access() {
        println("SensitiveObject accessed")
    }
}

class ProtectionProxy(private val userRole: String) : SensitiveObject {
    private val realObject: SensitiveObjectImpl = SensitiveObjectImpl()

    override fun access() {
        if (userRole == "admin") {
            realObject.access()
        } else {
            println("Access denied. Insufficient privileges.")
        }
    }
}

✅ 使用场景示例:

  • 后台管理接口仅允许 admin 角色调用
  • 敏感数据导出功能限制普通用户

⚠️ 注意:生产环境应结合 Spring Security 或自定义注解 + AOP 实现更完善的权限体系,此处仅为演示原理。


3.3. 日志代理(Logging Proxy)

拦截方法调用并记录日志信息,适用于调试、审计或性能监控。

interface ObjectToLog {
    fun operation()
}

class RealObjectToLog : ObjectToLog {
    override fun operation() {
        println("RealObjectToLog performing operation")
    }
}

class LoggingProxy(private val realObject: RealObjectToLog) : ObjectToLog {
    override fun operation() {
        println("Logging: Before operation")
        realObject.operation()
        println("Logging: After operation")
    }
}

📌 可扩展方向:

  • 记录耗时(measureTimeMillis
  • 输出参数/返回值(注意脱敏)
  • 集成 SLF4J 写入文件或发送到 ELK

❌ 不推荐在高频调用路径上做同步打印,会影响性能。


3.4. 远程代理(Remote Proxy)

代表位于不同地址空间(如远程服务器)的对象,使本地调用看起来像本地方法一样简单。

interface RemoteObject {
    fun performRemoteOperation()
}

class RemoteObjectImpl : RemoteObject {
    override fun performRemoteOperation() {
        println("RemoteObject performing remote operation on the server")
    }
}

class RemoteProxy(private val serverAddress: String) : RemoteObject {
    override fun performRemoteOperation() {
        println("Proxy: Initiating remote communication with server at $serverAddress")
        val remoteObject = RemoteObjectImpl()
        remoteObject.performRemoteOperation()
        println("Proxy: Remote communication complete")
    }
}

class Client(private val remoteObject: RemoteObject) {
    fun executeRemoteOperation() {
        println("Client: Performing operation through remote proxy")
        remoteObject.performRemoteOperation()
    }
}

class Server(private val remoteObject: RemoteObject) {
    fun startServer() {
        println("Server: Server started")
    }
}

remote proxies

📌 关键点总结:

  • ✅ 客户端通过 RemoteProxy 调用,完全 unaware 实际是远程调用
  • ✅ 代理封装了网络通信细节(序列化、连接、异常处理等)
  • 🔗 典型应用:RMI、gRPC Stub、Feign Client、Dubbo Invoker

📚 拓展阅读:Design patterns series


4. Kotlin 中代理模式的实际实现

我们通过一个图像加载的例子,直观对比是否使用代理带来的差异。

4.1. 未使用代理的情况

interface Image {
    fun display(): Unit
}

class RealImage(private val filename: String) : Image {
    init {
        loadFromDisk()
    }

    private fun loadFromDisk() {
        println("Loading image: $filename")
    }

    override fun display() {
        println("Displaying image: $filename")
    }
}

🔴 问题所在:

  • 构造即加载,即使后续不显示也会浪费 I/O 资源
  • 图像大时会造成初始化卡顿
  • 无法复用加载状态

4.2. 使用代理优化后的实现

引入 ProxyImage,实现懒加载和缓存。

interface Image {
    fun display(): Unit
}

class RealImage(private val filename: String) : Image {
    init {
        loadFromDisk()
    }

    private fun loadFromDisk() {
        println("Loading image: $filename")
    }

    override fun display() {
        println("Displaying image: $filename")
    }
}

class ProxyImage(private val filename: String) : Image {
    private var realImage: RealImage? = null

    override fun display() {
        if (realImage == null) {
            realImage = RealImage(filename)
        }
        realImage?.display()
    }
}

✅ 改进效果:

  • 第一次调用 display() 才触发加载 —— 懒加载
  • 后续调用直接使用已创建实例 —— 缓存复用
  • 对客户端透明,调用方式不变

📌 测试示例:

fun main() {
    val image: Image = ProxyImage("photo.jpg")
    image.display() // 加载 + 显示
    image.display() // 直接显示
}

输出:

Loading image: photo.jpg
Displaying image: photo.jpg
Displaying image: photo.jpg

5. 使用代理模式的优势

优势 说明
✅ 控制访问 可添加权限检查、频率限制、熔断机制等
✅ 功能增强 在不修改原类的前提下增加日志、缓存、监控等行为
✅ 资源管理 实现延迟加载、连接池管理、对象生命周期控制
✅ 解耦清晰 将横切逻辑从核心业务中剥离,提升模块化程度

一句话总结:让代理干脏活累活,本体专心做事


6. 使用代理模式的潜在问题

缺陷 风险说明 应对建议
❌ 增加复杂度 多一层封装,理解成本上升 合理命名类(如 XxxProxy)、写好文档
⚠️ 线程安全问题 多线程下可能重复创建或状态不一致 使用 synchronized@VolatileAtomicReference
❌ 耦合风险 客户端依赖代理可能导致重构困难 优先面向接口编程,配合 DI 框架降低耦合

📌 特别提醒:
若多个线程同时调用 ProxyImage.display(),存在 realImage 被多次初始化的风险。改进方案如下:

class ThreadSafeProxyImage(private val filename: String) : Image {
    @Volatile
    private var realImage: RealImage? = null

    override fun display() {
        if (realImage == null) {
            synchronized(this) {
                if (realImage == null) {
                    realImage = RealImage(filename)
                }
            }
        }
        realImage?.display()
    }
}

双重检查锁(Double-Checked Locking)+ @Volatile 是 Kotlin 中常见的线程安全延迟初始化写法。


7. 总结

代理模式在 Kotlin 开发中是一个非常实用的结构型模式,尤其适合以下场景:

  • ✅ 图片、大数据集的懒加载
  • ✅ 接口调用的权限控制
  • ✅ 方法级别的日志/监控埋点
  • ✅ 分布式系统的本地桩(Stub)

虽然会带来一定的复杂度,但只要合理设计、命名规范,并注意线程安全问题,就能充分发挥其“四两拨千斤”的威力。

最佳实践建议:
结合 Kotlin 的 by lazy、高阶函数、委托属性等特性,可以写出更简洁优雅的代理逻辑。但在大型项目中,也可考虑使用 AOP(如 Spring AOP 或 Koin AOP)来统一管理通用代理行为,避免重复编码。


原始标题:The Proxy Pattern in Kotlin