一、基本概念
定义
代理模式为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介作用,可以用于控制访问、延迟加载、增强功能等。
核心思想
- 间接访问:客户端不直接访问目标对象,而是通过代理访问
- 控制访问:代理可以控制对目标对象的访问
- 功能增强:在不修改目标对象的基础上,通过代理增加额外功能
三种代理类型
- 远程代理:为不同地址空间的对象提供本地代表
- 虚拟代理:延迟创建开销大的对象
- 保护代理:控制对原始对象的访问权限
二、基本结构
三个核心角色
// 1. Subject (抽象主题接口)
interface Image {
fun display()
fun load(): String
fun getFileName(): String
}
// 2. RealSubject (真实主题)
class RealImage(private val fileName: String) : Image {
init {
loadFromDisk()
}
private fun loadFromDisk() {
println("从磁盘加载大图片: $fileName")
Thread.sleep(2000) // 模拟耗时操作
}
override fun display() {
println("显示图片: $fileName")
}
override fun load(): String {
return "图片内容: $fileName"
}
override fun getFileName(): String = fileName
}
// 3. Proxy (代理)
class ProxyImage(private val fileName: String) : Image {
private var realImage: RealImage? = null
override fun display() {
// 延迟加载:只有真正需要时才创建真实对象
if (realImage == null) {
realImage = RealImage(fileName)
}
realImage?.display()
}
override fun load(): String {
// 缓存:避免重复加载
if (realImage == null) {
realImage = RealImage(fileName)
}
return realImage?.load() ?: ""
}
override fun getFileName(): String = fileName
}
// 客户端使用
fun main() {
val image: Image = ProxyImage("高清大图.jpg")
println("1. 第一次访问图片")
image.display() // 这里才会真正加载图片
println("\n2. 第二次访问同一张图片")
image.display() // 直接从内存缓存访问
println("\n3. 访问图片信息(无需加载)")
println("文件名: ${image.getFileName()}")
}
三、代理模式类型详解
1. 虚拟代理 (Virtual Proxy)
延迟创建开销大的对象
// Android中列表项的图片延迟加载
class LazyImageView(context: Context) : ImageView(context) {
private var imageUrl: String? = null
private var placeholder: Drawable? = null
private var errorImage: Drawable? = null
private var isLoading = false
fun loadImage(url: String, placeholderResId: Int = R.drawable.placeholder) {
imageUrl = url
placeholder = ContextCompat.getDrawable(context, placeholderResId)
// 先显示占位图
setImageDrawable(placeholder)
// 延迟加载(比如当图片进入屏幕可见区域时)
postDelayed({
if (!isLoading) {
startLoading()
}
}, 100)
}
private fun startLoading() {
isLoading = true
// 使用Glide等库异步加载
Glide.with(context)
.load(imageUrl)
.placeholder(placeholder)
.error(errorImage)
.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean
): Boolean {
isLoading = false
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean
): Boolean {
isLoading = false
return false
}
})
.into(this)
}
}
// 使用示例
val imageView = LazyImageView(context)
imageView.loadImage("https://example.com/large-image.jpg")
2. 保护代理 (Protection Proxy)
控制访问权限
// 文件系统访问权限控制
interface FileSystem {
fun readFile(fileName: String): String
fun writeFile(fileName: String, content: String)
fun deleteFile(fileName: String): Boolean
}
class RealFileSystem : FileSystem {
override fun readFile(fileName: String): String {
val file = File(fileName)
return file.readText()
}
override fun writeFile(fileName: String, content: String) {
File(fileName).writeText(content)
}
override fun deleteFile(fileName: String): Boolean {
return File(fileName).delete()
}
}
class ProtectedFileSystemProxy(
private val realFileSystem: RealFileSystem,
private val userRole: UserRole
) : FileSystem {
override fun readFile(fileName: String): String {
// 所有人都可以读
return realFileSystem.readFile(fileName)
}
override fun writeFile(fileName: String, content: String) {
if (userRole == UserRole.ADMIN || userRole == UserRole.EDITOR) {
realFileSystem.writeFile(fileName, content)
} else {
throw SecurityException("用户 ${userRole.name} 没有写权限")
}
}
override fun deleteFile(fileName: String): Boolean {
if (userRole == UserRole.ADMIN) {
return realFileSystem.deleteFile(fileName)
} else {
throw SecurityException("只有管理员可以删除文件")
}
}
enum class UserRole { GUEST, USER, EDITOR, ADMIN }
}
// 使用
fun main() {
val realFS = RealFileSystem()
val userProxy = ProtectedFileSystemProxy(realFS, UserRole.USER)
val adminProxy = ProtectedFileSystemProxy(realFS, UserRole.ADMIN)
// 用户只能读,不能写
println(userProxy.readFile("test.txt")) // 正常
// userProxy.writeFile("test.txt", "new") // 抛出SecurityException
// 管理员可以读写删
adminProxy.writeFile("test.txt", "new content") // 正常
adminProxy.deleteFile("test.txt") // 正常
}
3. 远程代理 (Remote Proxy)
访问远程对象
// 模拟网络API的远程代理
interface UserApi {
fun getUser(id: String): User
fun updateUser(user: User): Boolean
fun deleteUser(id: String): Boolean
}
data class User(val id: String, val name: String, val email: String)
class RemoteUserApiProxy(private val baseUrl: String) : UserApi {
private val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
private val service: UserApiService by lazy {
retrofit.create(UserApiService::class.java)
}
override fun getUser(id: String): User {
return try {
val response = service.getUser(id).execute()
if (response.isSuccessful) {
response.body()?.toDomain() ?: throw RuntimeException("用户不存在")
} else {
throw RuntimeException("网络请求失败: ${response.code()}")
}
} catch (e: Exception) {
// 可以在这里添加重试逻辑、缓存等
throw e
}
}
override fun updateUser(user: User): Boolean {
return try {
val response = service.updateUser(user.toDto()).execute()
response.isSuccessful
} catch (e: Exception) {
false
}
}
override fun deleteUser(id: String): Boolean {
return try {
val response = service.deleteUser(id).execute()
response.isSuccessful
} catch (e: Exception) {
false
}
}
// DTO转换
private fun User.toDto() = UserDto(id, name, email)
private fun UserDto.toDomain() = User(id, name, email)
// Retrofit接口
interface UserApiService {
@GET("/users/{id}")
fun getUser(@Path("id") id: String): Call<UserDto>
@PUT("/users/{id}")
fun updateUser(@Body user: UserDto): Call<Void>
@DELETE("/users/{id}")
fun deleteUser(@Path("id") id: String): Call<Void>
}
data class UserDto(val id: String, val name: String, val email: String)
}
// 使用
val api: UserApi = RemoteUserApiProxy("https://api.example.com")
val user = api.getUser("123")
4. 智能引用代理 (Smart Reference Proxy)
添加额外操作
// 数据库连接池 - 智能引用代理
interface DatabaseConnection {
fun executeQuery(sql: String): ResultSet
fun executeUpdate(sql: String): Int
fun close()
}
class RealDatabaseConnection(private val connection: Connection) : DatabaseConnection {
override fun executeQuery(sql: String): ResultSet {
return connection.createStatement().executeQuery(sql)
}
override fun executeUpdate(sql: String): Int {
return connection.createStatement().executeUpdate(sql)
}
override fun close() {
connection.close()
}
}
class PooledDatabaseConnectionProxy(
private val realConnection: RealDatabaseConnection,
private val connectionPool: ConnectionPool
) : DatabaseConnection {
private var isClosed = false
override fun executeQuery(sql: String): ResultSet {
checkNotClosed()
logQuery(sql)
return realConnection.executeQuery(sql)
}
override fun executeUpdate(sql: String): Int {
checkNotClosed()
logQuery(sql)
return realConnection.executeUpdate(sql)
}
override fun close() {
if (!isClosed) {
isClosed = true
// 不是真正关闭,而是归还到连接池
connectionPool.returnConnection(this)
}
}
fun realClose() {
realConnection.close()
}
private fun checkNotClosed() {
if (isClosed) {
throw IllegalStateException("连接已关闭")
}
}
private fun logQuery(sql: String) {
println("执行SQL: $sql")
// 可以添加性能监控、SQL注入检查等
}
}
class ConnectionPool {
private val pool = mutableListOf<PooledDatabaseConnectionProxy>()
private val maxSize = 10
fun getConnection(): DatabaseConnection {
return if (pool.isNotEmpty()) {
pool.removeAt(0).also { it.isClosed = false }
} else {
// 创建新连接
val realConn = createRealConnection()
PooledDatabaseConnectionProxy(realConn, this)
}
}
fun returnConnection(conn: PooledDatabaseConnectionProxy) {
if (pool.size < maxSize) {
pool.add(conn)
} else {
conn.realClose() // 真正关闭连接
}
}
private fun createRealConnection(): RealDatabaseConnection {
// 创建真实数据库连接
return RealDatabaseConnection(DriverManager.getConnection("jdbc:mysql://localhost/test"))
}
}
四、Android中的代理模式应用
1. Retrofit的动态代理
// Retrofit内部使用Java动态代理
class SimpleRetrofit {
fun <T> create(service: Class<T>): T {
return Proxy.newProxyInstance(
service.classLoader,
arrayOf(service),
InvocationHandler { proxy, method, args ->
// 解析注解
val annotation = method.getAnnotation(GET::class.java)
?: throw IllegalArgumentException("方法必须有HTTP注解")
val url = annotation.value
println("将调用方法: ${method.name}, URL: $url")
// 模拟网络请求
"模拟响应数据"
}
) as T
}
}
// 使用
interface GitHubApi {
@GET("/users/{user}/repos")
fun listRepos(@Path("user") user: String): String
}
fun main() {
val retrofit = SimpleRetrofit()
val api = retrofit.create(GitHubApi::class.java)
val result = api.listRepos("octocat")
println(result)
}
2. View的点击事件代理
// 点击事件防抖代理
interface OnClickListener {
fun onClick(view: View)
}
class DebounceClickListenerProxy(
private val realListener: OnClickListener,
private val debounceTime: Long = 500L
) : OnClickListener {
private var lastClickTime: Long = 0
override fun onClick(view: View) {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime >= debounceTime) {
lastClickTime = currentTime
realListener.onClick(view)
} else {
Log.d("Debounce", "点击过快,已忽略")
}
}
}
// 使用
button.setOnClickListener(object : View.OnClickListener {
private val proxy = DebounceClickListenerProxy(
realListener = object : OnClickListener {
override fun onClick(view: View) {
// 真正的点击逻辑
Toast.makeText(view.context, "按钮被点击", Toast.LENGTH_SHORT).show()
}
}
)
override fun onClick(view: View) {
proxy.onClick(view)
}
})
五、Kotlin中的代理特性
1. 类委托 (Class Delegation)
// 使用by关键字实现代理
interface Printer {
fun print(message: String)
fun printLine(message: String)
}
class RealPrinter : Printer {
override fun print(message: String) {
println("打印: $message")
}
override fun printLine(message: String) {
println("打印行: $message")
}
}
// 代理类,将大部分方法委托给realPrinter
class LoggingPrinter(private val realPrinter: RealPrinter) : Printer by realPrinter {
override fun print(message: String) {
println("[LOG] 开始打印: $message")
realPrinter.print(message)
println("[LOG] 打印完成")
}
// printLine方法直接使用realPrinter的实现
}
// 使用
val printer: Printer = LoggingPrinter(RealPrinter())
printer.print("Hello")
printer.printLine("World")
2. 属性委托 (Property Delegation)
// 自定义属性代理
class ObservableProperty<T>(
private var value: T,
private val onChange: (oldValue: T, newValue: T) -> Unit
) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
println("获取属性 ${property.name} = $value")
return value
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
val oldValue = value
value = newValue
println("设置属性 ${property.name}: $oldValue -> $newValue")
onChange(oldValue, newValue)
}
}
class User {
var name: String by ObservableProperty("默认") { old, new ->
println("名字从 '$old' 改为 '$new'")
}
var age: Int by ObservableProperty(0) { old, new ->
require(new >= 0) { "年龄不能为负数" }
println("年龄从 $old 改为 $new")
}
}
// 使用
val user = User()
user.name = "张三" // 输出: 设置属性 name: 默认 -> 张三
println(user.name) // 输出: 获取属性 name = 张三
3. 标准库中的属性代理
class Configuration {
// 延迟初始化
val heavyObject by lazy {
println("初始化heavyObject")
HeavyObject()
}
// 可观察属性
var username by Delegates.observable("默认用户") { _, old, new ->
println("用户名从 '$old' 改为 '$new'")
}
// 带否决权的可观察属性
var score by Delegates.vetoable(0) { _, old, new ->
println("尝试修改分数: $old -> $new")
new >= 0 // 只有新值>=0时才允许修改
}
// 非空属性,延迟初始化
var token: String by Delegates.notNull()
}
// 使用
val config = Configuration()
println(config.heavyObject) // 第一次访问时初始化
config.username = "新用户"
config.score = -10 // 不会被设置
println(config.score) // 仍然是0
六、动态代理实现
1. Java动态代理
// 接口
interface UserService {
fun addUser(name: String): Boolean
fun deleteUser(id: Int): Boolean
fun getUser(id: Int): String
}
// 真实实现
class UserServiceImpl : UserService {
override fun addUser(name: String): Boolean {
println("添加用户: $name")
return true
}
override fun deleteUser(id: Int): Boolean {
println("删除用户: $id")
return true
}
override fun getUser(id: Int): String {
return "用户$id"
}
}
// 动态代理处理器
class LoggingInvocationHandler(private val target: Any) : InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
println("开始执行方法: ${method.name}")
println("参数: ${args?.joinToString()}")
val startTime = System.currentTimeMillis()
val result = if (args != null) {
method.invoke(target, *args)
} else {
method.invoke(target)
}
val endTime = System.currentTimeMillis()
println("方法执行完成,耗时: ${endTime - startTime}ms")
println("返回值: $result")
return result
}
}
// 使用
fun main() {
val realService = UserServiceImpl()
val handler = LoggingInvocationHandler(realService)
val proxyService = Proxy.newProxyInstance(
UserService::class.java.classLoader,
arrayOf(UserService::class.java),
handler
) as UserService
proxyService.addUser("张三")
proxyService.getUser(1)
}
2. 带缓存的动态代理
class CachingInvocationHandler(private val target: Any) : InvocationHandler {
private val cache = mutableMapOf<String, Any>()
override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
// 生成缓存key
val key = buildCacheKey(method, args)
// 只缓存get开头的方法
if (method.name.startsWith("get") && cache.containsKey(key)) {
println("从缓存获取: $key")
return cache[key]
}
println("执行方法: ${method.name}")
val result = if (args != null) {
method.invoke(target, *args)
} else {
method.invoke(target)
}
// 缓存结果
if (method.name.startsWith("get") && result != null) {
cache[key] = result
println("缓存结果: $key")
}
return result
}
private fun buildCacheKey(method: Method, args: Array<out Any>?): String {
return method.name + (args?.joinToString(",") ?: "")
}
}
七、代理模式的优缺点
✅ 优点
- 职责清晰:真实主题只关注业务逻辑,代理处理附加功能
- 高扩展性:可以在不修改真实主题的情况下,通过代理增加功能
- 保护目标对象:代理可以控制对真实对象的访问
- 降低耦合度:客户端与真实对象解耦
- 开闭原则:可以方便地增加新的代理,无需修改现有代码
❌ 缺点
- 增加系统复杂度:引入了额外的代理层
- 性能开销:代理调用会增加处理时间
- 可能造成请求变慢:特别是远程代理,网络延迟可能较大
- 可能过度设计:对于简单场景,使用代理可能过于复杂
八、与其他模式的对比
代理模式 vs 装饰器模式
// 代理模式:控制访问,通常一对一关系
class ImageProxy : Image {
private val realImage: RealImage // 明确知道具体类型
override fun display() {
// 控制访问:延迟加载、权限检查等
if (checkAccess()) {
realImage.display()
}
}
}
// 装饰器模式:动态添加功能,可以多层嵌套
class ImageDecorator(private val image: Image) : Image {
override fun display() {
// 增强功能:添加边框、阴影等
addBorder()
image.display()
addShadow()
}
}
代理模式 vs 适配器模式
// 代理模式:提供相同接口,控制访问
interface Database {
fun query(sql: String): Result
}
class DatabaseProxy : Database {
private val realDb = RealDatabase()
override fun query(sql: String): Result {
// 控制访问:连接池、日志等
logQuery(sql)
return realDb.query(sql)
}
}
// 适配器模式:转换接口,使不兼容的接口协同工作
class NewDatabaseAdapter(private val newDb: NewDatabase) : Database {
override fun query(sql: String): Result {
// 转换接口:将旧接口转换为新接口
val newQuery = convertSql(sql)
return convertResult(newDb.newQuery(newQuery))
}
}
九、最佳实践指南
何时使用代理模式?
✅ 适用场景:
- 远程代理:需要访问远程对象时
- 虚拟代理:需要延迟加载开销大的对象时
- 保护代理:需要控制访问权限时
- 智能引用:需要添加额外操作(如引用计数、缓存)时
- AOP场景:需要添加日志、监控、事务等横切关注点时
❌ 避免使用场景:
- 对象创建简单,无需控制访问
- 性能要求极高,不能接受代理开销
- 可以直接访问对象,无需中介
- 可以使用更简单的解决方案(如回调、观察者)
Android开发建议
- 合理使用Kotlin委托:简化代理实现
- 注意内存泄漏:代理持有Context或Activity引用时要小心
- 性能考虑:远程代理要考虑网络状况,虚拟代理要考虑加载时机
- 安全性:保护代理要正确实现权限检查
- 与框架结合:合理使用Retrofit、Glide等框架的代理机制
代码示例:综合应用
// 网络图片加载的完整代理实现
interface ImageLoader {
fun load(url: String, imageView: ImageView)
fun preload(url: String)
fun clearCache()
}
class RealImageLoader(private val context: Context) : ImageLoader {
override fun load(url: String, imageView: ImageView) {
Glide.with(context)
.load(url)
.into(imageView)
}
override fun preload(url: String) {
Glide.with(context)
.load(url)
.preload()
}
override fun clearCache() {
Glide.get(context).clearMemory()
GlobalScope.launch(Dispatchers.IO) {
Glide.get(context).clearDiskCache()
}
}
}
class SmartImageLoaderProxy(
private val realLoader: RealImageLoader,
private val context: Context
) : ImageLoader {
private val memoryCache = LruCache<String, Bitmap>(10 * 1024 * 1024)
private val diskCacheDir = File(context.cacheDir, "image_cache")
private val loadingUrls = mutableSetOf<String>()
init {
if (!diskCacheDir.exists()) {
diskCacheDir.mkdirs()
}
}
override fun load(url: String, imageView: ImageView) {
// 1. 检查内存缓存
memoryCache.get(url)?.let {
imageView.setImageBitmap(it)
return
}
// 2. 检查磁盘缓存
getFromDiskCache(url)?.let {
memoryCache.put(url, it)
imageView.setImageBitmap(it)
return
}
// 3. 避免重复加载
if (loadingUrls.contains(url)) {
return
}
loadingUrls.add(url)
// 4. 显示占位图
imageView.setImageResource(R.drawable.placeholder)
// 5. 异步加载
CoroutineScope(Dispatchers.IO).launch {
try {
val bitmap = downloadImage(url)
withContext(Dispatchers.Main) {
imageView.setImageBitmap(bitmap)
}
// 缓存
memoryCache.put(url, bitmap)
saveToDiskCache(url, bitmap)
} catch (e: Exception) {
withContext(Dispatchers.Main) {
imageView.setImageResource(R.drawable.error)
}
} finally {
loadingUrls.remove(url)
}
}
}
override fun preload(url: String) {
// 预加载到内存缓存
if (memoryCache.get(url) == null) {
CoroutineScope(Dispatchers.IO).launch {
try {
val bitmap = downloadImage(url)
memoryCache.put(url, bitmap)
} catch (e: Exception) {
// 忽略错误
}
}
}
}
override fun clearCache() {
memoryCache.evictAll()
diskCacheDir.listFiles()?.forEach { it.delete() }
realLoader.clearCache()
}
private fun downloadImage(url: String): Bitmap {
// 模拟下载
Thread.sleep(1000)
return Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888)
}
private fun getFromDiskCache(url: String): Bitmap? {
val file = File(diskCacheDir, url.hashCode().toString())
return if (file.exists()) {
BitmapFactory.decodeFile(file.absolutePath)
} else {
null
}
}
private fun saveToDiskCache(url: String, bitmap: Bitmap) {
val file = File(diskCacheDir, url.hashCode().toString())
file.outputStream().use {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
}
}
}
十、总结
代理模式是Android开发中极其重要的设计模式,在以下场景中特别有用:
- 性能优化:通过虚拟代理实现延迟加载,提升应用响应速度
- 权限控制:通过保护代理实现安全访问控制
- 网络通信:通过远程代理简化远程调用
- 缓存管理:通过智能引用代理实现缓存策略
- 横切关注点:通过代理统一处理日志、监控、事务等
关键点:
- 代理是对象的代表,客户端通过代理间接访问真实对象
- 控制访问是核心:代理可以控制何时、如何访问真实对象
- Kotlin委托简化实现:善用
by关键字和属性委托 - 合理选择代理类型:根据需求选择虚拟、保护、远程等代理
在Android中的实际应用:
- 图片加载:Glide、Picasso内部使用了代理模式
- 网络请求:Retrofit使用动态代理
- 权限管理:通过代理检查运行时权限
- 数据库访问:Room等ORM框架使用代理实现延迟加载
代理模式是构建健壮、可维护Android应用的重要工具,合理使用可以显著提升代码质量和应用性能。