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

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

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

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


一、虚拟代理

1.一、延迟加载

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

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

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

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

1.二、需求分析

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

常规作法是把这些信息所有从数据库查询出来,而后临时保存到数组进行显示。若是每一个人的信息很是多,那么就会致使内存消耗严重和比较长的查询时间。网络

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

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

这个需求就能够经过代理模式来实现

1.三、代理模式定义

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

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

可是真由于在客户端和真实对象之间加上了代理对象,那么此时咱们就能够干点别的事,好比控制权限等。这就是代理的主要做用。

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

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

1.四、UML结构图及说明

image

1.五、代码实现

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

建立代理和真实类的接口

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.一、需求分析

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

假设咱们如今有一个订单系统,须要实现以下两个需求:

一、每一个用户登陆进来,能够添加和修改本身的订单,可是对于他人的订单只能看不能改。

二、对于每次访问咱们都要记录下次数。

需求1可使用保护代理实现,需求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] 订单被查询12016-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] 订单被查询22016-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] 订单被查询32016-11-29 16:48:45.903 代理模式[11123:753185] 
订单名字:台灯订单
 订单操做员:张三
 订单数量:1000
 订单签定时间:2016-10-11复制代码

三、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实现消息转发-模块化的网络接口层设计


四、对比其余模式

image


五、Demo下载

代理模式Demo下载

相关文章
相关标签/搜索