面向对象设计的设计模式(十):代理模式

定义

代理模式(Proxy Pattern) :为某个对象提供一个代理,并由这个代理对象控制对原对象的访问。

定义解读:使用代理模式以后,客户端直接访问代理,代理在客户端和目标对象之间起到中介的作用。

适用场景

在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。

因为代理对象可以在客户端和目标对象之间起到中介的作用,因此可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。

根据业务的不同,代理也可以有不同的类型:

  • 远程代理:为位于不同地址或网络化中的对象提供本地代表。
  • 虚拟代理:根据要求创建重型的对象。
  • 保护代理:根据不同访问权限控制对原对象的访问。

下面来看一下代理模式的成员和类图。

成员与类图

成员

代理模式算上客户端一共有四个成员:

  • 客户端(Client):客户端意图访问真是主体接口
  • 抽象主题(Subejct):抽象主题定义客户端需要访问的接口
  • 代理(Proxy):代理继承于抽象主题,目的是为了它持有真实目标的实例的引用,客户端直接访问代理
  • 真实主题(RealSubject):真实主题即是被代理的对象,它也继承于抽象主题,它的实例被代理所持有,它的接口被包装在了代理的接口中,而且客户端无法直接访问真实主题对象。

其实我也不太清楚代理模式里面为什么会是Subject和RealSubject这个叫法。

下面通过类图来看一下各个成员之间的关系:

模式类图

代理模式类图

从类图中可以看出,工厂类提供一个静态方法:通过传入的字符串来制造其所对应的产品。

代码示例

场景概述

在这里举一个买房者通过买房中介买房的例子。

现在一般我们买房子不直接接触房东,而是先接触中介,买房的相关合同和一些事宜可以先和中介进行沟通。

在本例中,我们在这里让买房者直接支付费用给中介,然后中介收取一部分的中介费, 再将剩余的钱交给房东。

场景分析

中介作为房东的代理,与买房者直接接触。而且中介还需要在真正交易前做其他的事情(收取中介费,帮买房者check房源的真实性等等),因此该场景比较适合使用代理模式。

根据上面的代理模式的成员:

  • 客户端就是买房者
  • 代理就是中介
  • 真实主题就是房东
  • 中介和房东都会实现收钱的方法,我们可以定义一个抽象主题类,它有一个公共接口是收钱的方法。

代码实现

首先我们定义一下房东和代理需要实现的接口PaymentInterface(在类图里面是继承某个共同对象,我个人比较习惯用接口来做)。

//================== PaymentInterface.h ==================

@protocol PaymentInterface <NSObject>

- (void)getPayment:(double)money;

@end

这个接口声明了中介和房东都需要实现的方法getPayment:

接着我们声明代理类HouseProxy:

//================== HouseProxy.h ==================

@interface HouseProxy : NSObject<PaymentInterface>

@end




//================== HouseProxy.m ==================
const float agentFeeRatio = 0.35;

@interface HouseProxy()

@property (nonatomic, copy) HouseOwner *houseOwner;

@end

@implementation HouseProxy

- (void)getPayment:(double)money{

    double agentFee = agentFeeRatio * money;
    NSLog(@"Proxy get payment : %.2lf",agentFee);

    [self.houseOwner getPayment:(money - agentFee)];
}

- (HouseOwner *)houseOwner{

    if (!_houseOwner) {
         _houseOwner = [[HouseOwner alloc] init];
    }
    return _houseOwner;
}

@end

HouseProxy里面,持有了房东,也就是被代理者的实例。然后在的getPayment:方法里,调用了房东实例的getPayment:方法。而且我们可以看到,在调用房东实例的getPayment:方法,代理先拿到了中介费(中介费比率agentFeeRatio定义为0.35,即中介费的比例占35%)。

这里面除了房东实例的getPayment:方法之外的一些操作就是代理存在的意义:它可以在真正被代理对象做事情之前,之后做一些其他额外的事情。比如类似AOP编程一样,定义类似的before***Method或是after**Method方法等等。

最后我们看一下房东是如何实现getPayment:方法的:

//================== HouseOwner.h ==================

@interface HouseOwner : NSObject<PaymentInterface>

@end




//================== HouseOwner.m ==================

@implementation HouseOwner

- (void)getPayment:(double)money{

    NSLog(@"House owner get payment : %.2lf",money);
}

@end

房东类HouseOwner按照自己的方式实现了getPayment:方法。

很多时候被代理者(委托者)可以完全按照自己的方式去做事情,而把一些额外的事情交给代理来做,这样可以保持原有类的功能的纯粹性,符合开闭原则。

下面我们看一下客户端的使用以及打印出来的结果:

客户端代码:

//================== client.m ==================

HouseProxy *proxy = [[HouseProxy alloc] init];
[proxy getPayment:100];

上面的客户端支付给了中介100元。

下面我们看一下打印结果:

Proxy get payment : 35.00
House owner get payment : 65.00

和预想的一样,中介费收取了35%的中介费,剩下的交给了房东。

代码对应的类图

代理模式代码示例类图

从UML类图中我们可以看出,在这里没有使用抽象主题对象,而是用一个接口来分别让中介和房东实现。

优点

  • 降低系统的耦合度:代理模式能够协调调用者和被调用者,在一定程度上降低了系 统的耦合度。
  • 不同类型的代理可以对客户端对目标对象的访问进行不同的控制:
    • 远程代理,使得客户端可以访问在远程机器上的对象,远程机器 可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
    • 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
    • 保护代理可以控制客户端对真实对象的使用权限。

缺点

  • 由于在客户端和被代理对象之间增加了代理对象,因此可能会让客户端请求的速度变慢。

Objective-C & Java的实践

  • iOS SDK:NSProxy可以为持有的对象进行消息转发
  • JDK:AOP下的JDKDynamicAopProxy是对JDK的动态代理进行了封装

原文发布于微信公众号 - 程序员维他命(J_Knight_)

原文发表时间:2019-04-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券