简述: 从这篇文章起,我们将继续Kotlin邂逅设计模式系列篇中的第二篇代理模式。代理模式可以说很多初级中级开发者迷惑的设计模式。但是它确实应用很广,不用多说大家非常熟悉的Retrofit框架,内部使用了动态代理设计模式,以注解的方式简化网络请求参数传递,从而实现更高解耦。然而在Kotlin中有天然支持的属性代理语法特性,可以简化Java中代理模式实现的模板代理。
一、介绍
代理模式(Proxy Pattern),又称委托模式,顾名思义就是一个对象的实现委托给另一个代理对象来实现供外部调用。
二、定义
为其他对象提供一种代理方式来控制对某个对象的访问,从而更好地保证了该对象对外使用的透明性。
三、基本要求
- 1、委托对象(或者被代理对象)与代理对象需要实现相同的接口。
- 2、代理对象中保有实际的委托对象引用,外部调用的操作或行为都是代理对象在内部交于实际的委托对象去实现。
- 3、为了内部隐藏性,外部调用者直接和两者共同的接口通信。
四、使用场景
当无法或不想直接访问某个对象或访问某个对象存在困难时可以通过一个代理对象来间接访问。代理可以实现方法增强,比如常用的日志,缓存等;也可以实现方法拦截,通过代理方法修改原方法的参数和返回值
五、UML类图
代理模式在生活中非常常见,由于最近身边同事都在讨论买房,这里就以买房中介为例来介绍我们今天的代理模式。首先我们需要使用UML类图直观地表示出代理模式思想。
由上面的UML的类图可知,主要涉及到四种角色:
- 1、Client: 客户类,可以看做代理模式调用的外部者
- 2、IPurchaseHouse: 抽象买房接口,该接口主要职责是声明HouseOwner(实际房子拥有者)与HouseAgent(房产中介)的共同接口方法,该类可以是一个接口或抽象类
- 3、HouseOwner: 房子拥有者(房东),也就是代理模式中实际委托对象或被代理对象,外部调用者Client类就是通过代理对象(中介)间接调用实际的委托对象中定义的方法
- 4、HouseAgent: 房产中介,也就是代理模式中的代理对象,该类持有一个真实HouseOwner引用,在代理类中接口方法中调用HouseOwner方法以此来达到代理作用。
六、静态代理
1、Java实现静态代理
在Java中实现静态代理还是比较简单,只要按照上述UML中分析角色规则来定义就能轻松实现。这里就用Java先去实现上述例子:
//IPurchaseHouse: 抽象买房接口
interface IPurchaseHouse {
void inquiryPrice();//询价
void visitHouse();//看房
void payDeposit();//付定金
void signAgreement();//签合同
void payMoney();//付钱
void getHouse();//拿房
}
//HouseOwner: 房子拥有者(房东)
class HouseOwner implements IPurchaseHouse {
//实现IPurchaseHouse共同接口
@Override
public void inquiryPrice() {
System.out.println("HouseOwner提出房子价格: 200W RMB");
}
@Override
public void visitHouse() {
System.out.println("HouseOwner同意买房者来看房子");
}
@Override
public void payDeposit() {
System.out.println("HouseOwner收了买房者1W RMB定金");
}
@Override
public void signAgreement() {
System.out.println("HouseOwner与买房者签订合同");
}
@Override
public void payMoney() {
System.out.println("买房者付钱给HouseOwner");
}
@Override
public void getHouse() {
System.out.println("买房者拿到房子");
}
}
//HouseAgent: 房产中介
class HouseAgent implements IPurchaseHouse {
private IPurchaseHouse mHouseOwner;//具体房东HouseOwner被代理对象引用
public HouseAgent(IPurchaseHouse houseOwner) {
mHouseOwner = houseOwner;
}
@Override
public void inquiryPrice() {
mHouseOwner.inquiryPrice();//通过具体房东HouseOwner引用去调用inquiryPrice
}
@Override
public void visitHouse() {
mHouseOwner.visitHouse();//通过具体房东HouseOwner引用去调用visitHouse
}
@Override
public void payDeposit() {
mHouseOwner.payDeposit();//通过具体房东HouseOwner引用去调用payDeposit
}
@Override
public void signAgreement() {
mHouseOwner.signAgreement();//通过具体房东HouseOwner引用去调用signAgreement
}
@Override
public void payMoney() {
mHouseOwner.payMoney();//通过具体房东HouseOwner引用去调用payMoney
}
@Override
public void getHouse() {
mHouseOwner.getHouse();//通过具体房东HouseOwner引用去调用getHouse
}
}
//Client客户类
class Client {
public static void main(String[] args) {
IPurchaseHouse houseOwner = new HouseOwner();
IPurchaseHouse houseAgent = new HouseAgent(houseOwner);//传入具体被代理类实例
houseAgent.inquiryPrice();//询问价格
houseAgent.visitHouse();//看房
houseAgent.payDeposit();//支付定金
houseAgent.signAgreement();//签合同
houseAgent.payMoney();//付钱
houseAgent.getHouse();//拿房
}
}
运行结果:
HouseOwner提出房子价格: 200W RMB
HouseOwner同意买房者来看房子
HouseOwner收了买房者1W RMB定金
HouseOwner与买房者签订合同
买房者付钱给HouseOwner
买房者拿到房子
Process finished with exit code 0
这就是静态代理具体的实现,可能有些并不能看到代理模式所带来的好处,看上去就像是代理类做了实际转发调用而已。实际上有个很明显优点就是: 可以在HouseAgent类中整个流程插入一些特有的操作或行为,而不会影响内部HouseOwner的实现,保护内部的实现。 还有一个优点就是代理类在保证HouseOwner核心功能同时可以扩展其他行为。
上述结论可能有点抽象,假如现在有个不一样需求比如A房产中介,在看房之前首先得签订一个看房协议,但是这个协议只涉及购买用户与中介之间的协议。所以基于代理模式很轻松就实现。
//修改后的HouseAgentA
class HouseAgentA implements IPurchaseHouse {
private IPurchaseHouse mHouseOwner;//具体房东HouseOwner被代理对象引用
private boolean mIsSigned;
public HouseAgentA(IPurchaseHouse houseOwner) {
mHouseOwner = houseOwner;
}
@Override
public void inquiryPrice() {
mHouseOwner.inquiryPrice();//通过具体房东HouseOwner引用去调用inquiryPrice
}
@Override
public void visitHouse() {
if (mIsSigned) {
System.out.println("您已经签订了看房协议,可以看房了");
mHouseOwner.visitHouse();//通过具体房东HouseOwner引用去调用visitHouse
} else {
System.out.println("很抱歉,您还没签订了看房协议,暂时不能看房");
}
}
public void signVisitHouseAgreement(boolean isSigned) {
mIsSigned = isSigned;
}
@Override
public void payDeposit() {
mHouseOwner.payDeposit();//通过具体房东HouseOwner引用去调用payDeposit
}
@Override
public void signAgreement() {
mHouseOwner.signAgreement();//通过具体房东HouseOwner引用去调用signAgreement
}
@Override
public void payMoney() {
mHouseOwner.payMoney();//通过具体房东HouseOwner引用去调用payMoney
}
@Override
public void getHouse() {
mHouseOwner.getHouse();//通过具体房东HouseOwner引用去调用getHouse
}
}
//Client客户类
class Client {
public static void main(String[] args) {
IPurchaseHouse houseOwner = new HouseOwner();
IPurchaseHouse houseAgent = new HouseAgentA(houseOwner);//传入具体被代理类实例
houseAgent.inquiryPrice();//询问价格
((HouseAgentA) houseAgent).signVisitHouseAgreement(true);//签订看房合同
houseAgent.visitHouse();//看房
houseAgent.payDeposit();//支付定金
houseAgent.signAgreement();//签合同
houseAgent.payMoney();//付钱
houseAgent.getHouse();//拿房
}
}
运行结果:
HouseOwner提出房子价格: 200W RMB
您已经签订了看房协议,可以看房了
HouseOwner同意买房者来看房子
HouseOwner收了买房者1W RMB定金
HouseOwner与买房者签订合同
买房者付钱给HouseOwner
买房者拿到房子
Process finished with exit code 0
2、Kotlin实现静态代理
看到了Java中的HouseAgent和HouseAgent中代理类中实现转发委托是不是有点无脑啊,有点机械,就像是在写Java中的setter和getter方法一样,太多的样板代码。这时候把它叫给Kotlin吧,它会让你的代理类看起来更加简洁和优雅,因为在Kotlin中实现代理模式有着天然优势,熟悉Kotlin的小伙伴们都知道,在Kotlin中有代理独有语法特性,通过它就能轻松实现代理模式。
//IPurchaseHouseKt: 抽象买房接口
interface IPurchaseHouseKt {
fun inquiryPrice() //询价
fun visitHouse() //看房
fun payDeposit() //付定金
fun signAgreement() //签合同
fun payMoney() //付钱
fun getHouse() //拿房
}
//HouseOwnerKt: 房子拥有者(房东)
class HouseOwnerKt : IPurchaseHouseKt {
override fun inquiryPrice() {
println("HouseOwner提出房子价格: 200W RMB")
}
override fun visitHouse() {
println("HouseOwner同意买房者来看房子")
}
override fun payDeposit() {
println("HouseOwner收了买房者1W RMB定金")
}
override fun signAgreement() {
println("HouseOwner与买房者签订合同")
}
override fun payMoney() {
println("买房者付钱给HouseOwner")
}
override fun getHouse() {
println("买房者拿到房子")
}
}
//HouseAgentKt: 房产中介. 注意了,重点来了,Kotlin只需要简单一行就替代了Java代理类所有样板代码
class HouseAgentKt(houseOwnerKt: IPurchaseHouseKt) : IPurchaseHouseKt by houseOwnerKt//通过by关键字实现代理,省略大量的代理类中的样板代码,这一点需要get
//Client调用处
fun main(args: Array<String>) {
val houseOwnerKt = HouseOwnerKt()
HouseAgentKt(houseOwnerKt).run {
inquiryPrice()//询问价格
visitHouse()//看房
payDeposit()//支付定金
signAgreement()//签合同
payMoney()//付钱
getHouse()//拿房
}
}
运行结果:
HouseOwner提出房子价格: 200W RMB
HouseOwner同意买房者来看房子
HouseOwner收了买房者1W RMB定金
HouseOwner与买房者签订合同
买房者付钱给HouseOwner
买房者拿到房子
Process finished with exit code 0
可能有的小伙伴就会问了,你使用by关键字一下把所有的方法都给代理了,可是需要像上面新加的需求一样,需要在某个方法调用时插入一段逻辑。这个也非常方便,只需要重写需要改变的那个方法即可。一起来瞅瞅:
<