设计模式篇(九)——代理模式

一、简介

代理模式(Proxy Pattern) 是一种 结构型设计模式,它的存在就是是为了减轻其他类的工作,作为一个好人好事的代表,我们也应该多学习学习他的精神。

定义: 一个类替另一个类做事情。(添🐕不得🏠)

使用场景: 作为一个女神,每天都是很忙的,每件事情不可能都亲力而为,所以女神只需要吹个口哨,就会有一堆舔狗帮她做事;

实现要素:

  1. 一定要学会 静态代理动态代理
  2. 静态代理 不一定要按照标准来。

代理模式分为 静态代理动态代理,其中 静态代理 没必要追求标准,设计模式的精髓就是随机应变!按照设计模的 定义,你所书写的代码都是合格的设计模式。动态代理 是个很牛逼很牛逼很牛逼的东西!!!

二、实现

小胡同学是个很有想法的人,这天他突然想创业做一个国产最牛逼的单机游戏,但是他只会编剧本,不会敲代码、美工,所以他就通过金钱交易,招人替他做这些事情。

1、标准实现

第一步先设计出主类和代理类的共同接口:

interface MakeGameAble{
    //  <剧本>
    fun script()
    //  <写代码>
    fun code()
    //  <美工>
    fun art()
}

第二步分别实现主类和代理类:

object Boss:MakeGameAble{

    private val coder:BillGates
    private val artist:Picasso

    init {
        coder = BillGates()
        artist = Picasso()
    }

    /**
     *  <制作游戏>
     */
    fun makeGame(){
        script()
        code()
        art()
    }

    override fun script() {
        println("我写剧本老牛逼了!")
    }

    override fun code() {
        println("要真是会敲代码,头发也不会这么茂盛~")
        println("交给比尔盖茨了:")
        coder.code()
    }

    override fun art() {
        println("浑身都是艺术细菌~")
        println("交给毕加索了:")
        artist.art()
    }
}

class BillGates:MakeGameAble{
    override fun script() { //println("剧本是什么?") }

    override fun code() {
        println("十分钟给你撸个Windows")
    }

    override fun art() { //println("能花钱买吗?") }
}

class Picasso:MakeGameAble{

    override fun script() { //println("我大学学的不是这个~") }

    override fun code() { //println("计算机是什么?") }

    override fun art() {
        println("我只会做抽象的图。")
    }
}

Main 方法中:

fun main() {
    Boss.makeGame()
}

控制台输出:

我写剧本老牛逼了!
要真是会敲代码,头发也不会这么茂盛~
交给比尔盖茨了:
十分钟给你撸个Windows
浑身都是艺术细菌~
交给毕加索了:
我只会做抽象的图。

2、精髓实现

回到 代理模式定义: 一个类替另一个类做事情。我们完全可以不必按照标准来,死板的定义一个公共接口,一些 不可抗力的历史因素可能不会给你重新设计的机会,随机应变才是王道。

下面我就写个很简单的例子:

object Boss{

    private val company:Outsourced

    init {
        company = Outsourced()
    }

    /**
     *  <制作游戏>
     */
    fun makeGame(){
        company.makeShit()
    }
}

/**
 *  <外包公司>
 */
class Outsourced{

    fun makeShit(){
        println("输出:奥斯卡级别的剧本")
        println("输出:德福~纵享丝滑的代码")
        println("输出:能拍卖一个亿的图")
        println("输出:屎一样的作品~")
    }
}
3、重要的话(必看)

事实上,实现任何一种模式,都可以有简单的实现、复杂的实现、优雅的实现、屎一样的实现。结合使用场景,挑选出最合适的实现方式。

根据六大设计原则中:开闭原则,对外扩展是允许的,但是对内修改的拒绝的。当你有了一定的代码量经验后,你肯定会无比的赞同这句话。当你去到一家新公司工作,或者面对自己以前的代码,那些陌生又熟悉,但是又不得不修改的代码,你肯定会很纠结!对内完全修改?怕是你不知道被后面接二连三的bug鞭挞是什么滋味~那我们应该怎么办呢?

答:遵循 开闭原则,已有的闭环或者封装良好的代码,尽量不要修改,如果真的需要修改,比如说修改某个方法,那就 新增 一个满足你需求的方法,然后在代码中将旧的方法的引用替换成你新增的方法。

利用 代理模式,你就可以以最少的浸入式代码,去完成你想达到的目的。

4、动态代理(必学)

如果说 静态代理 是女神的小舔狗,女神要它干什么,它就干什么,而 动态代理 就是劫匪,强迫女神干一些事情,比如说拍女神抠鼻屎的照片发到微博上去……

动态代理 可以借助 Java 提供的类(当然也可以不用),将代理类实现 InvocationHandler,实现其 invoke方法,然后再借助 Proxy 类通过反射创建代理类。

我们以餐馆为例,餐馆上菜需要厨师做菜,所以我们先定义一个 厨师类

interface Cook{
    fun cookFood(food:String)
}

class Cooker:Cook{

    override fun cookFood(food:String){
        println("制作食物:$food")
    }
}

然后定义一个 餐馆类

class Restaurant{

    var cooker:Cooker

    init {
        cooker = Cooker()
    }

    /**
     *  <上菜>
     */
    fun serve(unit:Int,food:String){
        println("${unit}号桌上菜:")
        cooker.cookFood(food)
    }
}

先在 Main 方法中看一下效果:

fun main() {
    val restaurant = Restaurant()
    restaurant.serve(1,"肉蛋葱鸡")
}

控制台输出:

1号桌上菜:
制作食物:肉蛋葱鸡

这时候有个小二说:主厨太菜了,我觉得我做的菜比他好吃一万倍,我要做主厨!但是得耍个小手段让老板炒了他,于是就想悄咪咪的把主厨做的菜替换成狗屎~

class XiaoEr(val cooker: Cooker):InvocationHandler {

    override fun invoke(proxy: Any?, method: Method, args: Array<out Any>?): Any? {
        println("本来制作的食物:${args?.get(0) as String}")
        var result = method.invoke(cooker,"狗屎")
        return result
    }
}

好,我们先来看看能不能成功:

fun main() {

    val restaurant = Restaurant()
    val cooker = restaurant.cooker

    val xiaoEr = Proxy.newProxyInstance(cooker::class.java.classLoader,
        arrayOf<Class<*>>(Cook::class.java)
        ,XiaoEr(cooker)) as Cook

    xiaoEr.cookFood("肉蛋葱鸡")
}

控制台输出:

本来制作的食物:肉蛋葱鸡
制作食物:狗屎

看样子是成功了,那接下来就是趁主厨不注意,替换成主厨,然后 “帮”他制作食物:

fun main() {
	... ... //之前的代码都一样
	//xiaoEr.cookFood("肉蛋葱鸡") 删除这一行
    restaurant.cooker = xiaoEr	//将主厨替换成自己
    restaurant.serve(1,"肉蛋葱鸡")
}

控制台输出:

1号桌上菜:
本来制作的食物:肉蛋葱鸡
制作食物:狗屎

5、加强版动态代理(必学)

或许有的小老板发现了,你这 动态代理 好麻烦啊,限制超多:

  1. 替换的方法必须是类的接口方法。
  2. 类的成员变量(上例中的cooker)必须是接口类型(因为Proxy.newInstance()只能强转成其第二个参数中的接口类型),你才能替换这个成员变量。
  3. 这个成员变量还得开放访问权限。

第三个限制我们可以通过反射来解决,但是前两个是致命伤,因为我们需尽量不要修改代码,又或者是系统的API、开源的三方库,这些我们是不能修改的。那么我们如何解决这些问题呢?

当然是反射啦!

我们先来制造一个环境:

//	删除接口
open class Cooker{

    open fun cookFood(food:String){
        println("制作食物:$food")
    }
}

class Restaurant{
	//私有化属性
    private val cooker:Cooker

    init {
        cooker = Cooker()
    }
    ... ...
}

这样我们就无法通过 Proxy 动态生成代理类了(因为Cooker没有接口……)。看接下来一顿猛如虎的操作,同样是先创建一个 小二

//	想要成为厨子,那就继承厨子,并且在构造方法中持有需要替换的对象
class XiaoEr(val cooker: Cooker): Cooker() {

    override fun cookFood(food: String) {
    	//	<拿到替换对象的方法,并且偷偷换成狗屎>
        val cookMethod = cooker.javaClass.getMethod("cookFood",String::class.java)
        cookMethod.invoke(cooker, "狗屎")
    }
}

接下来写一个将 小二 替换 主厨 的方法:

fun replaceCooker(restaurant: Restaurant) {
    val cookerField = restaurant.javaClass.getDeclaredField("cooker")
    cookerField.isAccessible = true
    //	<拿到主厨>
    val cooker = cookerField.get(restaurant) as Cooker
    //	<创建小二>
    val xiaoEr = XiaoEr(cooker)
    //	<替换>
    cookerField.set(restaurant,xiaoEr)
}

让我们来看一下效果:

fun main() {
    val restaurant = Restaurant()
    replaceCooker(restaurant)

    restaurant.serve(1, "肉蛋葱鸡")
}

控制台输出:

1号桌上菜:
制作食物:狗屎

在这里插入图片描述

但是, 这个方法还是有限制,眼见的小伙伴可能就会注意到:

  1. 原类(Cooker)前面加了 open 关键词,使其能够被继承。
  2. 原方法(cookFood() )前面也加了 open 关键词,使其能够被重写。

所以就是这两个限制了,在开发过程中,如果除去这两个限制,发现某个类满足条件,你就可以猥琐欲为……

三、相关源码

这就多了去了,例如诸多打印日志的框架……跟踪某个组件或者类,来打印日志,例如 ActivityFragment

这里挖个坑,以后会传一份打印日志的代码。

四、小结

静态代理 让类的重心更倾向于自己的实现,很符合 单一职责,更关键是提供了一种减少代码量的方式……

动态代理 给我们提供了发挥骚操作的方式,你想要多骚就能多骚,让你实现自己的钩子,你也能够 Hook 啦~

最后总结成一句话:代理模式 用的好,保住头发没烦恼……
再见

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值