美文网首页
代理模式 (Proxy Pattern)

代理模式 (Proxy Pattern)

作者: 筱湮 | 来源:发表于2023-01-07 19:54 被阅读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

    相关文章

      网友评论

          本文标题:代理模式 (Proxy Pattern)

          本文链接:https://www.haomeiwen.com/subject/rcgktdtx.html