设计模式系列5--代理模式

今天我们来学习下什么是代理模式和如何运用它去解决一些常见的问题,代理模式大概分为如下几大类:

  1. 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又称为大使(Ambassador)。
  2. 虚拟代理(Virtual Proxy):如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
  3. 保护代理(Protect Proxy):控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限.
  4. 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
  5. 智能引用代理(Smart Reference Proxy):当一个对象被引用时,提供一些额外的操作,例如将对象被调用的次数记录下来等。

今天我们学习下其中几个常用的:虚拟代理、保护代理、智能引用代理。


1、虚拟代理

1.1、延迟加载

虚拟代理主要是用来做延迟加载用的,以一个简单的示例来阐述使用代理模式实现延迟加载的方法及其意义。假设某客户端软件有根据用户请求去数据库查询数据的功能。在查询数据前,需要获得数据库连接,软件开启时初始化系统的所有类,此时尝试获得数据库连接。当系统有大量的类似操作存在时 (比如 XML 解析等),所有这些初始化操作的叠加会使得系统的启动速度变得非常缓慢。为此,使用代理模式的代理类封装对数据库查询中的初始化操作,当系统启动时,初始化这个代理类,而非真实的数据库查询类,而代理类什么都没有做。因此,它的构造是相当迅速的。

在系统启动时,将消耗资源最多的方法都使用代理模式分离,可以加快系统的启动速度,减少用户的等待时间。而在用户真正做查询操作时再由代理类单独去加载真实的数据库查询类,完成用户的请求。这个过程就是使用代理模式实现了延迟加载。

延迟加载的核心思想是:如果当前并没有使用这个组件,则不需要真正地初始化它,使用一个代理对象替代它的原有的位置,只要在真正需要的时候才对它进行加载。使用代理模式的延迟加载是非常有意义的,首先,它可以在时间轴上分散系统压力,尤其在系统启动时,不必完成所有的初始化工作,从而加速启动时间;其次,对很多真实主题而言,在软件启动直到被关闭的整个过程中,可能根本不会被调用,初始化这些数据无疑是一种资源浪费。例如使用代理类封装数据库查询类后,系统的启动过程这个例子。若系统不使用代理模式,则在启动时就要初始化 DBQuery 对象,而使用代理模式后,启动时只需要初始化一个轻量级的对象 DBQueryProxy。

在iOS开发一个典型的开发场景就是:一个tableview列表需要从网络下载很多图片显示,如果等到全部下载完毕再显示,用户体验会很不好。一个替代方法就是首次加载时显示一个占位图,当后台线程下载完图片,再用真实图片去替代原来的占位图。这就是上面说的延迟加载。

1.2、需求分析

我们来看一个实际需求,假设我们有一个列表需要一次显示100个人的信息,但是列表显示的只有用户的名字和性别,当用户点击某个具体的人的时候,才会显示该人的完整的信息。

常规做法是把这些信息全部从数据库查询出来,然后临时保存到数组进行显示。如果每个人的信息非常多,那么就会导致内存消耗严重和比较长的查询时间。

其实我们分析下就知道,用户很少会查看所有的个人全部信息,用户感兴趣的可能就只有几个人,那么一次把用户全部信息都加载到内存会导致浪费,而且每个人的信息比较多的情况,全部查询这些信息页会导致查询时间过长。

解决方案就是:我们可以在首次加载的时候只从数据库查询到name和sex信息存储起来给用户展示,当用户点击某个人的时候才会再次请求数据库去获取完整的信息。

这个需求就可以通过代理模式来实现

1.3、代理模式定义

为其他对象提供一种代理来提供对这个对象的控制访问

通过上面的定义可以发现代理模式其实就是创建一个代理对象,用这个代理对象去代替真实对象,客户端操作这个代理对象进行的操作,最终会被代理对象转发到真实对象。

但是真因为在客户端和真实对象之间加上了代理对象,那么此时我们就可以干点别的事,比如控制权限等。这就是代理的主要作用。

根据代理不同的用途,可以分为文字开头的几大类。再分析下上面的需求如何使用代理模式来实现:

首次加载我们只显示一个代理对象,这个代理对象只加载name和sex,当用户需要查看该人的全部信息的时候,我们才会把请求转发到真实对象,去显示所有个人信息。

1.4、UML结构图及说明

1.5、代码实现

下面我们来看看使用代理模式的如何实现上述功能

创建代理和真实类的接口

subject.h文件  
=====================

@protocol subject <NSObject>

-(NSString *)getName;
-(NSInteger)getAge;
-(NSString *)getSex;
-(NSString *)getAddress;
-(NSString *)getCountry;

//首次加载获取简单信息:name和sex
-(void)getSimpleInfo;
//当用户点击了某个人,去数据库获取该人的全部信息
-(void)getCompleteInfo;

@end

创建代理类

#import <Foundation/Foundation.h>
#import "subject.h"
#import "realSubject.h"

@interface proxy : NSObject<subject>
- (instancetype)initWithRealSubject:(realSubject *)subject;

@end


=============

#import "proxy.h"

@interface proxy()
@property(strong,nonatomic)realSubject *realSubject;
@property(assign,nonatomic)BOOL isReload;
@end

@implementation proxy
- (instancetype)initWithRealSubject:(realSubject *)subject
{
    self = [super init];
    if (self) {
        self.realSubject = subject;
    }
    return self;
}


-(NSString *)getSex{
   NSLog(@"性别:%@",[self.realSubject getSex]);
    return [self.realSubject getSex];
}

-(NSString*)getName{
    NSLog(@"名字:%@", [self.realSubject getName]);
    return [self.realSubject getName];

}

-(NSInteger)getAge{
    if (!self.isReload)
    {
        [self reloadDB];
    }
    NSLog(@"年龄:%zd", [self.realSubject getAge]);
    return [self.realSubject getAge];
}


-(NSString *)getAddress{
    if (!self.isReload)
    {
        [self reloadDB];
    }

   NSLog(@"地址:%@",[self.realSubject getAddress]);
    return [self.realSubject getAddress];

}


-(NSString *)getCountry{
    if (!self.isReload)
    {
        [self reloadDB];
    }

    NSLog(@"国家:%@",[self.realSubject getCountry]);
    return  [self.realSubject getCountry];

}

-(void)reloadDB{
    self.isReload = YES;
    //假设下面的数据是从数据库重新查询到的数据
    self.realSubject.age = 19;
    self.realSubject.address = @"泰坦星球";
    self.realSubject.country =  @"赛亚王国";

}

-(void)getSimpleInfo{
    NSLog(@"查询数据库获取简单数据....");
    self.realSubject.name = @"张三";
    self.realSubject.sex = @"男";
    [self getName];
    [self getSex];
}


-(void)getCompleteInfo{
    NSLog(@"重新查询数据库获取全部数据....");
    [self getName];
    [self getSex];
    [self getCountry];
    [self getAddress];
    [self getAge];
}

@end

创建真实类

#import <Foundation/Foundation.h>
#import "subject.h"

@interface realSubject : NSObject<subject>
//真实环境有几十条属性,这里为了方便只展示几条属性
@property(nonatomic,strong)NSString *name;
@property(nonatomic,assign)NSInteger age;
@property(nonatomic,strong)NSString *sex;
@property(nonatomic,strong)NSString *address;
@property(nonatomic,strong)NSString *country;


@end

================
#import "realSubject.h"

@implementation realSubject

-(NSString *)getSex{
    return self.sex;
}

-(NSString *)getName{
    return self.name;

}

-(NSInteger)getAge{
    return self.age;
}


-(NSString *)getAddress{
    return self.address;
}


-(NSString *)getCountry{
    return self.country;
}


@end

客户端调用:

    proxy *pro = [[proxy alloc]initWithRealSubject:[realSubject new]];
    //先获取简单的数据,此时只有name和age字段
    [pro getSimpleInfo];
    //获取完整的数据,包括所有信息
    [pro getCompleteInfo];

输出:

2016-11-29 16:48:45.901 代理模式[11123:753185] 查询数据库获取简单数据....  
2016-11-29 16:48:45.901 代理模式[11123:753185] 名字:张三  
2016-11-29 16:48:45.901 代理模式[11123:753185] 性别:男  
2016-11-29 16:48:45.901 代理模式[11123:753185] 重新查询数据库获取全部数据....  
2016-11-29 16:48:45.902 代理模式[11123:753185] 名字:张三  
2016-11-29 16:48:45.902 代理模式[11123:753185] 性别:男  
2016-11-29 16:48:45.902 代理模式[11123:753185] 国家:赛亚王国  
2016-11-29 16:48:45.902 代理模式[11123:753185] 地址:泰坦星球  
2016-11-29 16:48:45.902 代理模式[11123:753185] 年龄:19

这里为了简单,只展示了一个用户的信息,首次只获取了name和sex,当需要获取全部信息的时候,会再次查询数据库去获取完整数据。


2、保护代理和智能引用

2.1、需求分析

保护代理主要是对原有对象加上一层权限控制,根据访问者权限来决定访问者可以进行哪些操作

假设我们现在有一个订单系统,需要实现如下两个需求:

1、每个用户登录进来,可以添加和修改自己的订单,但是对于他人的订单只能看不能改。

2、对于每次访问我们都要记录下次数。

需求1可以使用保护代理实现,需求2使用智能引用代理实现

具体实现过程就是:在用户需求对某个订单进行修改的时候,先用代理来判断该用户是否是订单所有人,是就可以修改然后把修改请求转发到真实数据库操作对象,不是就禁止修改,不转发请求。每次代理查询请求发送到真是对象之前,先进行计数操作。

直接看代码实现

2.2、代码实现

创建代理和真实对象的接口

@protocol orderInterface <NSObject>

-(void)changeProductName:(NSString *)productName operator:(NSString *)opreator;
-(void)queryOrder;
@end

创建代理对象

#import <Foundation/Foundation.h>
#import "orderInterface.h"
#import "order.h"

@interface orderProxy : NSObject<orderInterface>
- (instancetype)initWithOrder:(order* )order;
@end

================================================

#import "orderProxy.h"


@interface orderProxy()
@property(strong,nonatomic)order * ord;
@end

static NSInteger orderQueryCount;

@implementation orderProxy
- (instancetype)initWithOrder:(order* )order
{
    self = [super init];
    if (self) {
        self.ord = order;
    }
    return self;
}

-(void)changeProductName:(NSString *)productName operator:(NSString *)opreator{
    if([opreator isEqualToString:self.ord.orderOperator]){
        NSLog(@"修改订单成功");
        [self.ord changeProductName:productName operator:opreator];
    }else{
        NSLog(@"你无权操作该订单");
    }
}



-(void)queryOrder{
    orderQueryCount ++;
    NSLog(@"订单被查询%zd次", orderQueryCount);
    [self.ord queryOrder];

}


@end

创建真实对象

#import <Foundation/Foundation.h>
#import "orderInterface.h"

@interface order : NSObject<orderInterface>
@property(strong,nonatomic)NSString *orderOperator;
@property(strong,nonatomic)NSString *productName;
@property(assign,nonatomic)NSInteger productAmount;
@property(strong,nonatomic)NSString *orderSignTime;


- (instancetype)initWithName:(NSString *)operator name:(NSString *)name amount:(NSInteger)amount time:(NSString *)time;
@end

=====================


#import "order.h"
@implementation order
- (instancetype)initWithName:(NSString *)operator name:(NSString *)name amount:(NSInteger)amount time:(NSString *)time
{
    self = [super init];
    if (self) {
        self.orderOperator = operator;
        self.productName = name;
        self.productAmount = amount;
        self.orderSignTime = time;
    }
    return self;
}


-(void)changeProductName:(NSString *)productName operator:(NSString *)opreator{
    self.productName = productName;
}

-(void)queryOrder{
    NSLog(@"\n订单名字:%@\n 订单操作员:%@\n 订单数量:%zd\n 订单签订时间:%@",self.productName,self.orderOperator,self.productAmount,self.orderSignTime);
}


@end

客户端调用:

        order *ord = [[order alloc]initWithName:@"张三" name:@"电脑订单" amount:1000 time:@"2016-10-11"];
        orderProxy *proxy = [[orderProxy alloc]initWithOrder:ord];
        [proxy changeProductName:@"办公椅订单" operator:@"李四"];
        [proxy queryOrder];

        [proxy changeProductName:@"办公椅订单" operator:@"张三"];
        [proxy queryOrder];

        [proxy changeProductName:@"台灯订单" operator:@"张三"];
        [proxy queryOrder];

输出显示:

2016-11-29 16:48:45.902 代理模式[11123:753185] 你无权操作该订单  
2016-11-29 16:48:45.902 代理模式[11123:753185] 订单被查询1次  
2016-11-29 16:48:45.902 代理模式[11123:753185]  
订单名字:电脑订单
 订单操作员:张三
 订单数量:1000
 订单签订时间:2016-10-11
2016-11-29 16:48:45.902 代理模式[11123:753185] 修改订单成功  
2016-11-29 16:48:45.902 代理模式[11123:753185] 订单被查询2次  
2016-11-29 16:48:45.902 代理模式[11123:753185]  
订单名字:办公椅订单
 订单操作员:张三
 订单数量:1000
 订单签订时间:2016-10-11
2016-11-29 16:48:45.902 代理模式[11123:753185] 修改订单成功  
2016-11-29 16:48:45.902 代理模式[11123:753185] 订单被查询3次  
2016-11-29 16:48:45.903 代理模式[11123:753185]  
订单名字:台灯订单
 订单操作员:张三
 订单数量:1000
 订单签订时间:2016-10-11

3、iOS实现代理模式

其实iOS已经内置了代理的实现,我们只需要使用NSProxy类的两个方法就可以实现代理模式的功能。

-(void)forwardInvocation:(NSInvocation *)anInvocation

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

其实就是runtime的消息转发,因为oc里面的方法访问本质是消息的转发,所以可以使用上面两个方法变相实现代理模式。

NSObject类也有这两个方法用来做消息转发,那么他们有什么区别呢?

具体看这篇文章:

使用NSProxy和NSObject设计代理类的差异

一般要实现实现代理功能都是继承下NSProxy,然后实现上面两个方法就可以了。

我们来把需求2的功能改成使用NSProxy来实现。只需要把orderProxy类换成如下所示即可:

#import <Foundation/Foundation.h>
#import "orderInterface.h"
#import "order.h"

@interface orderProxy : NSProxy<orderInterface>
- (instancetype)initWithOrder:(order* )order;
@end


========

#import "orderProxy.h"


@interface orderProxy()
@property(strong,nonatomic)order * ord;
@end

static NSInteger orderQueryCount;

@implementation orderProxy
- (instancetype)initWithOrder:(order* )order
{
    self.ord = order;
    return self;
}


-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if([self.ord respondsToSelector:aSelector] ){
        return [self.ord methodSignatureForSelector:aSelector];
    }else{
        return [super methodSignatureForSelector:aSelector];
    }
}

-(void)forwardInvocation:(NSInvocation *)anInvocation{
    NSString *selName = NSStringFromSelector(anInvocation.selector);

    if([self.ord respondsToSelector:anInvocation.selector] &&  [selName isEqualToString:@"changeProductName:operator:"]){
        NSString *opreator ;
        [anInvocation getArgument:&opreator atIndex:3];//self和_cmd分别是参数0和1,所有后面的参数index从2开始,这里取的是operator参数,index=3
        if([opreator isEqualToString:self.ord.orderOperator]){
            NSLog(@"修改订单成功");
            [anInvocation invokeWithTarget:self.ord];
        }else{
            NSLog(@"你无权操作该订单");
        }
    }else if ([self.ord respondsToSelector:anInvocation.selector] &&  [selName isEqualToString:@"queryOrder"]){
        orderQueryCount ++;
        NSLog(@"订单被查询%zd次", orderQueryCount);
        [anInvocation invokeWithTarget:self.ord];
    }
    else{
        [super forwardInvocation:anInvocation];
    }

}

@end

关于NSProxy更高级的用法可以看看这篇文章:

利用NSProxy实现消息转发-模块化的网络接口层设计


4、对比其他模式


5、Demo下载

代理模式Demo下载

comments powered by Disqus