一、简介
代理模式(Proxy Pattern) 是一种 结构型设计模式,它的存在就是是为了减轻其他类的工作,作为一个好人好事的代表,我们也应该多学习学习他的精神。
定义: 一个类替另一个类做事情。(添🐕不得🏠)
使用场景: 作为一个女神,每天都是很忙的,每件事情不可能都亲力而为,所以女神只需要吹个口哨,就会有一堆舔狗帮她做事;
实现要素:
- 一定要学会 静态代理 和 动态代理。
- 静态代理 不一定要按照标准来。
代理模式分为 静态代理 和 动态代理,其中 静态代理 没必要追求标准,设计模式的精髓就是随机应变!按照设计模的 定义,你所书写的代码都是合格的设计模式。动态代理 是个很牛逼很牛逼很牛逼的东西!!!
二、实现
小胡同学是个很有想法的人,这天他突然想创业做一个国产最牛逼的单机游戏,但是他只会编剧本,不会敲代码、美工,所以他就通过金钱交易,招人替他做这些事情。
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、加强版动态代理(必学)
或许有的小老板发现了,你这 动态代理 好麻烦啊,限制超多:
- 替换的方法必须是类的接口方法。
- 类的成员变量(上例中的cooker)必须是接口类型(因为
Proxy.newInstance()只能强转成其第二个参数中的接口类型
),你才能替换这个成员变量。 - 这个成员变量还得开放访问权限。
第三个限制我们可以通过反射来解决,但是前两个是致命伤,因为我们需尽量不要修改代码,又或者是系统的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号桌上菜:
制作食物:狗屎
但是, 这个方法还是有限制,眼见的小伙伴可能就会注意到:
- 原类(Cooker)前面加了 open 关键词,使其能够被继承。
- 原方法(
cookFood()
)前面也加了 open 关键词,使其能够被重写。
所以就是这两个限制了,在开发过程中,如果除去这两个限制,发现某个类满足条件,你就可以猥琐欲为……
三、相关源码
这就多了去了,例如诸多打印日志的框架……跟踪某个组件或者类,来打印日志,例如 Activity、Fragment。
这里挖个坑,以后会传一份打印日志的代码。
四、小结
静态代理 让类的重心更倾向于自己的实现,很符合 单一职责,更关键是提供了一种减少代码量的方式……
而 动态代理 给我们提供了发挥骚操作的方式,你想要多骚就能多骚,让你实现自己的钩子,你也能够 Hook 啦~
最后总结成一句话:代理模式 用的好,保住头发没烦恼……