設計模式系列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下載

相關文章
相關標籤/搜索