代理模式 (Proxy Pattern)

2023-01-07  本文已影响0人  筱湮

说明:本文为《设计模式之禅》的阅读笔记,主要总结精华和记录自己的部分理解。

1. 定义

Provide a surrogate or placeholder for another object to control access to it.
为其他对象提供一种代理以控制对这个对象的访问。

代理模式也叫做委托模式,它是一项基本设计技巧。许多其他的模式,如状态模式、策略模式、访问者模式本质上是在更特殊的场合采用了委托模式,而且在日常的应用中,代理模式可以提供非常好的访问控制。

通俗理解:委托模式,就是一个对象的实现委托给另一个对象来实现,然后供外部调用。

代码清单1 代理模式示例

// 业务类接口
interface Subject {
    fun request()
}


// 真实业务逻辑类
class RealSubject : Subject {
    override fun request() {
        println("demo.RealSubject 处理业务逻辑")
    }
}


/**
 * @param subject 要代理的实现类
 */
class Proxy(private val subject: Subject) : Subject {

    override fun request() {
        this.before()
        this.subject.request()
        this.after()
    }

    // 预处理
    private fun before() {
        println("demo.Proxy 预处理request()")
    }

    // 后处理
    private fun after() {
        println("demo.Proxy 后处理request()")
    }
}

2. 基本要求

3. 使用场景

为什么要使用代理模式?

4. 代理模式的优点

5. 代理模式的扩展

5.1 普通代理

5.1.1 定义

调用者必须知道代理类的存在,使用代理类去访问想要访问的内容。
调用者只能访问代理角色,而不能访问真实角色。

5.2 强制代理

5.2.1 定义

调用者直接调用真实角色,而不用关心代理是否存在,其代理的产生是由真实角色决定的。

5.3 静态代理

生活中有个场景非常常见,大部分人都有过此体会,租房!这里就以这个场景为例来实现一下代理模式。
先用类图直观表达出代理模式的思想。

代理模式.png

由图可知主要涉及到四种角色:

  1. Client:客户类,可以看做是外部调用者
  2. IRentOutHouse:抽象买房接口,该接口的主要职责是声明 HouseOwner (房东) 与 HouseAgent(房屋中介)的共同接口方法,该类可以是一个接口或抽象类
  3. HouseOwner:房东,也就是代理模式中实际委托对象或被代理对象,外部调用者 Client 类就是通过代理对象 (房屋中介) 间接调用实际的委托对象中定义的方法
  4. HouseAgent:房屋中介,也就是代理模式中的代理对象,该类持有一个真实 HouseOwner 的引用,在代理类的接口方法中调用 HouseOwner 对应的方法,以此来达到代理作用。

代码清单2 静态代理

// 出租房屋接口
interface IRentOutHouse {

    fun setPrice() // 定价

    fun showingHouse() // 展示房屋

    fun getDeposit() // 获取定金

    fun signContract() // 签合同

    fun getRent() // 获取租金

    fun provideKey() // 交接钥匙
}

// 房东
class HouseOwner: IRentOutHouse {

    override fun setPrice() {
        println("HouseOwner 设定租金为2000/月")
    }

    override fun showingHouse() {
        println("HouseOwner 同意来看房子")
    }

    override fun getDeposit() {
        println("HouseOwner 获取定金")
    }

    override fun signContract() {
        println("HouseOwner 签合同")
    }

    override fun getRent() {
        println("HouseOwner 获取租金")
    }

    override fun provideKey() {
        println("HouseOwner 交接钥匙")
    }
}

// 房屋中介
// 在Kotlin中实现代理模式有天然优势,只需要一行代码就可以消除Java代理类中所有样板代码
// 通过by关键字实现代理,省略了大量的样板代码,需要get✔️
class HouseAgent(private val houseOwner: HouseOwner) : IRentOutHouse by houseOwner


// Client调用处
fun main(args: Array<String>) {
    val houseOwner = HouseOwner()
    HouseAgent(houseOwner).run {
        setPrice()
        showingHouse()
        getDeposit()
        signContract()
        getRent()
        provideKey()
    }
}

运行结果:

HouseOwner 设定租金为2000/月
HouseOwner 同意来看房子
HouseOwner 获取定金
HouseOwner 签合同
HouseOwner 获取租金
HouseOwner 交接钥匙

Process finished with exit code 0

上述HouseAgent中,使用by关键字后所有方法都被代理了,如果需要在某个方法调用时新加逻辑,只需要重写对应的方法即可。示例如下:

// 房屋中介
// 在Kotlin中实现代理模式有天然优势,只需要一行代码就可以消除Java代理类中所有样板代码
// 通过by关键字实现代理,省略了大量的样板代码,需要get✔️
class HouseAgent(private val houseOwner: HouseOwner) : IRentOutHouse by houseOwner {
    override fun showingHouse() {
        houseOwner.showingHouse()
        println("HouseAgent 领看房")
    }

    override fun getDeposit() {
        println("HouseAgent 代收定金")
        houseOwner.getDeposit()
    }

    override fun signContract() {
        println("HouseAgent 提供租房协议")
        houseOwner.signContract()
    }

    override fun getRent() {
        houseOwner.getRent()
        println("HouseAgent 获取佣金")
    }

    override fun provideKey() {
        houseOwner.provideKey()
        println("HouseAgent 代房东交接钥匙")
    }
}

运行结果:

HouseOwner 设定租金为2000/月
HouseOwner 同意来看房子
HouseAgent 领看房
HouseAgent 代收定金
HouseOwner 获取定金
HouseAgent 提供租房协议
HouseOwner 签合同
HouseOwner 获取租金
HouseAgent 获取佣金
HouseOwner 交接钥匙
HouseAgent 代房东交接钥匙

Process finished with exit code 0

5.4 动态代理

如上述的方式,自己写代理的方式就是静态代理。
动态代理是,在实现的阶段不关心代理谁,运行阶段才指定具体代理哪个对象。

// InvocationHandler是JDK提供的动态代理接口,对被代理类的方法进行代理。
// obj-被代理类的实例
class RentOutHouseIH(private val obj: Any) : InvocationHandler {

    // 调用被代理的方法
    // 因为传参不确定,所以用*args.orEmpty()
    override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
        return method?.invoke(obj, *args.orEmpty())
    }
}

// Client调用处
fun main(args: Array<String>) {
    val houseOwner = HouseOwner()
    val rentOutHouseIH = RentOutHouseIH(houseOwner)
    // 动态产生一个代理者
    // Proxy.newProxyInstance方法动态构造一个代理中介,需要传入被代理类的ClassLoader、共同接口集合和dynamicProxy实例对象
    val dynamicProxy = Proxy.newProxyInstance(
        houseOwner.javaClass.classLoader,
        arrayOf(IRentOutHouse::class.java),
        rentOutHouseIH
    ) as? IRentOutHouse
    dynamicProxy?.run {
        setPrice()
        showingHouse()
        getDeposit()
        signContract()
        getRent()
        provideKey()
    }
}

运行结果:

HouseOwner 设定租金为2000/月
HouseOwner 同意来看房子
HouseOwner 获取定金
HouseOwner 签合同
HouseOwner 获取租金
HouseOwner 交接钥匙

Process finished with exit code 0

在这个过程中,既没有新建代理类,也没有实现IRentOutHouse接口,这就是动态代理。

此时如果我们在客户去看房后被通知有人去看房了,有什么好办法呢?好办法如下:

class RentOutHouseIH(private val obj: Any) : InvocationHandler {

    // 调用被代理的方法
    // 因为传参不确定,所以用*args.orEmpty()
    override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
        val result = method?.invoke(obj, *args.orEmpty())
        if (method?.name?.equals("showingHouse") == true) {
            println("有客户去看房了")
        }
        return result
    }
}

运行结果

HouseOwner 设定租金为2000/月
HouseOwner 同意来看房子
有客户去看房了
HouseOwner 获取定金
HouseOwner 签合同
HouseOwner 获取租金
HouseOwner 交接钥匙

Process finished with exit code 0

这样每次有客户去看房我们都会收到通知了!

附1:思维导图


代理模式 (Proxy Pattern).png

附2:代码实现 https://github.com/ooxiaoyan/ProxyPattern

上一篇下一篇

猜你喜欢

热点阅读