最近接了个大项目,哈哈哈~ 文章终于出来啦~ 还好没胎死腹中。最近的感悟就是,做好小事,才有机会做大事,承担大项目!
最近在学 Kotlin 基础,发现动态代理这块之前还是没搞明白,所以就学了下 Kotlin 中的代理模式写法,发现东西有点多,遂独自成文,欢迎大家拍砖!
引子
动态代理主要是用来干什么的?通俗一点,就是你在调用其他类的一些方法时,想加入你自己的一些处理逻辑。比如说,统计这些方法的执行时长等,这也是面向切面编程的思想。
代理模式
动态代理,源自于设计模式中常见的一种模式:代理模式。在 Java 中就是为一个对象 A 的一个方法 B 提供一个代理对象,这个代理对象可以完全控制 A 对象的 B 方法实际的执行内容。四个关键词:1)代理对象;2)被代理对象;3)被代理行为;4)对行为的完全控制。
这样说还是太抽象,举个实际的例子。假如我们需要通过房屋中介租房,就是一个简单的代理模式。这里面有三种角色:1)房东——被代理对象;2)房屋中介——代理对象;3)租客——使用方或调用者。房东把全部的租房事项全部交给中介打理,那么中介就具有对租房行为的完全控制,租客只能跟中介打交道。
看看上面的四个关键词,中介 对应 代理对象;房东 对应 被代理对象;租房相关事项 对应 被代理行为,比如找租客、约看房时间、看房、签约等;中介可以完全控制对租房事项的行为 对应 对行为的完全控制,比如中介可以加价,提供保洁服务等。
代理模式的基本要素
- 代理对象与被代理对象需要实现相同的接口。即对于出租房屋来说,就是出租房屋的相关事项,房东和中介都必须完成的出租流程手续。
- 代理对象中有被代理对象的引用,这样外部调用者在调用代理对象的方法时,代理对象就会在内部交给被代理对象去实际执行。即,中介可以代替房东去出租房屋,最终决定权还是在房东手里。
- 外部调用者是直接使用接口进行调用的,对被代理对象的信息有一定的保护作用。即,租客只能通过出租房屋的流程手续与中介进行沟通,并不知道房东的个人信息。
租房代码实例 - Java 版
Talk is cheap,Show me your Code! 我们具体来看看代码怎么表示。 首先是租房的一些流程,也就是被代理的行为,这里为方便只写了三个步骤。通常用抽象类或接口的形式表示:
// code 1 interface IRentHouse { // 带领租客看房 void visitHouse(); // 讨价还价 void argueRent(int rent); // 签合同 void signAgreement(); }
其次,房东的实现,也就是被代理对象的行为,房东的权利。
// code 2 class HouseOwner implements IRentHouse{ @Override public void visitHouse() { System.out.println("HouseOwner 带领看房,介绍房屋特点"); } @Override public void argueRent(int rent) { System.out.println("HouseOwner 提出租金为:" + rent); } @Override public void signAgreement() { System.out.println("HouseOwner 签合同"); } }
接下来就是房屋中介的实现,因为中介得到了房东的授权,所以他可以有房东的所有权利,他代理房东去操作出租房屋的相关事项。因此,中介会先征求房东的同意,拿到授权,在代码中的表现就是持有房东的引用。
// code 3 public class HouseAgent implements IRentHouse{ IRentHouse mHouseOwner; // 中介持有房东的权利 public HouseAgent(IRentHouse houseOwner) { mHouseOwner = houseOwner; } @Override public void visitHouse() { mHouseOwner.visitHouse(); } @Override public void argueRent(int rent) { mHouseOwner.argueRent(rent); } @Override public void signAgreement() { mHouseOwner.signAgreement(); } }
在 main 方法中就可以如下调用了:
// code 4 HouseAgent agent = new HouseAgent(new HouseOwner()); // 传入房东对象,拿到授权 agent.visitHouse(); agent.argueRent(300); agent.signAgreement();
这其实就是一个静态代理的例子。为啥是“静态”?因为这里的代理方法是写死的,如果 HouseOwner 对象新增方法,那么在 HouseAgent 和 接口中都得新增代码,不够灵活。
那么代理模式有什么作用?看起来好像就是代理类对被代理类做了一层封装而已,其实这层封装在一定程度上保护了被代理类的信息,使用者不用关心内部的具体实现。此外,代理类可以根据自身特点和客户需求进行功能扩展,在保证 HouseOwner 类的核心方法正确执行的情况下,扩展其他的行为。例如:租户刚毕业社会经验不足,遇到了一个黑心中介,需要在你看房前给小费:
// code 5 public class HouseAgent implements IRentHouse{ IRentHouse mHouseOwner; // 中介持有房东的权利 int mTip = 0; // 小费 public HouseAgent(IRentHouse houseOwner) { mHouseOwner = houseOwner; } @Override public void visitHouse() { if (mTip > 10) { mHouseOwner.visitHouse(); } else { System.out.println("小费不够,暂不能看房"); } } @Override public void argueRent(int rent) { mHouseOwner.argueRent(rent); } @Override public void signAgreement() { mHouseOwner.signAgreement(); } public void giveTip(int tip) { mTip = tip; } }
租房代码实例 - Kotlin 版
上面静态代理的 Java 代码写起来虽然容易,但是套路都一样,显得繁琐。Kotlin 代码中,使用 by 关键字就可以了,非常方便,还是上面的例子,Kotlin 代码为:
// code 6 interface IRentHouse { // 带领租客看房 fun visitHouse() // 讨价还价 fun argueRent(rent: Int) // 签合同 fun signAgreement() } class HouseOwner: IRentHouse { override fun visitHouse() { println("带领看房,介绍房屋特点") } override fun argueRent(rent: Int) { println("提出租金为:$rent") } override fun signAgreement() { println("签合同") } } class HouseAgent(houseOwner: IRentHouse): IRentHouse by houseOwner {} // 调用的代码如下 HouseAgent(HouseOwner()).visitHouse() HouseAgent(HouseOwner()).argueRent(500) HouseAgent(HouseOwner()).signAgreement()
一个 by 关键字,就避免写很多重复的代码了。如果代理类要在原有的基础上添加自己的方法或功能,只需要在代理类中重写被代理对象中的方法。还是拿上面的例子说明:
// code 7 class HouseAgent(houseOwner: IRentHouse): IRentHouse by houseOwner { var mTip = 0 private val mHouseOwner = houseOwner fun giveTip(tip: Int){ mTip = tip } override fun visitHouse() { if (mTip > 10) { mHouseOwner.visitHouse() } else { println("小费不够,暂不能看房") } } }
所以,静态代理就是把代理关系的代码是写死的,若被代理对象添加了新的方法,那么代理类和接口都需要改动。当然如果是用 Kotlin 写,那么只需要对接口进行改动。但是,如果我们想要切面编程,在每个被代理对象的方法中添加自己的处理,比如,我需要知道每个方法步骤的执行用时,那么就得在每个方法调用前后记录时间戳,这就很繁琐了。而且每新增方法都得重复写相同的代码,这种情况下,就需要使用动态代理。