代理模式(Proxy Pattern)

代理模式:为另外一个对象提供一个替身或占位符,以控制该对象的访问。

代理模式是结构型模式之一。代理模式应该算是一个应用比较常见的设计模式了,是必须掌握的一种设计模式。

代理模式
为什么需要代理模式?

通过代理模式来简化逻辑的一个很通用的方法。我们不需要知道彼此的业务逻辑。只要处理你要干什么,你告知给我(代理)然后我去找人(RealSubject)帮你实现就好。

比如说我们要展示服务端的数据。我们告知我们的代理类,代理去请求服务端的数据。代理拿到数据后通知我们展示数据的对象。我们不需要关心服务端的具体内部逻辑。

代理分类

1.静态代理
1).普通的代理:类图展示的代理类型,也是最容易理解的代理类型。

2).远程代理 :调用代理类,会被代理转发到远程执行,并且结果会通过返回给代理,再由代理告知给我们调用请求的对象。(上面说的请求数据就是远程代理)


远程代理

3).虚拟代理:直到我们真正需要一个对象的时候才会创建它。在对象创建前和创建中的过程中时由虚拟对象来扮演真实对象替身。对象创建后,再由代理委托给真实对象。


虚拟代理

2.动态代理:在运行时动态的创建代理类,实现多个接口,并将方法转发给指定的类。
动态代理的运用,保护代理。

保护代理

举个栗子
简单的代理模式没啥好说的,这里提一个保护代理的栗子,保护代理能懂,简单的代理也就懂了。

相亲网站上每个人的信息都在上面公开,你可以根据你的喜好去找一个你喜欢的人。你也可以把你的信息贴出来,让别人来选择。

既然如此,那用户信息都能修改,如果有人改了你兴趣爱好,填上一些恶趣味的爱好? 貌似有些可怕。。。
如果说不让用户修改,那别人怎么对你进行评价?这也是一个问题

所以貌似应该是这样,对自己的信息可以修改但不能修改评价,对别人对信息不能修改,但可以修改评价。

怎么样保证?

我们对所有对数据流进行拦截。要访问(读取/写入)数据必须经过我们的保护代理,保护代理保证写入数据的安全性。

如果不能理解保护代理类图的,现在应该懂了。

用户信息

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol UserInfo  <NSObject>

@property (nonatomic ,strong)NSString * name;
@property (nonatomic ,assign)NSInteger age;
//0 为男 , 1 为女
@property (nonatomic ,assign)BOOL sex;
@property (nonatomic ,copy)NSString * hobit;
@property (nonatomic ,strong)NSString * publicExperice;
//评分
@property (nonatomic ,assign)NSInteger grade;
@end

NS_ASSUME_NONNULL_END

用户的具体实现(getter/setter)方法

#import "LocalUser.h"

@implementation LocalUser
@synthesize name = _name;
@synthesize age = _age;
@synthesize sex = _sex;
@synthesize hobit = _hobit;
@synthesize publicExperice = _publicExperice;
@synthesize grade = _grade;

-(void)setName:(NSString *)name {
    
    _name = name;
}

-(NSString *)name {
    
    return _name;
}

-(void)setAge:(NSInteger)age {
    
    _age = age;
}
-(NSInteger)age {
    
    return _age;
}

-(void)setSex:(BOOL)sex {
    
    _sex = sex;
}

-(BOOL)sex {
    
    return _sex;
}

-(void)setHobit:(NSString *)hobit {
    
    _hobit = hobit;
}

-(NSString *)hobit {
    
    return _hobit;
}

-(void)setPublicExperice:(NSString *)publicExperice {
    
    _publicExperice = publicExperice;
}

-(NSString *)publicExperice {
    
    return _publicExperice;
}

-(void)setGrade:(NSInteger)grade {
    
    _grade = grade;
}

-(NSInteger)grade {
    return _grade;
}

@end

动态代理需要遵循的协议

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@protocol Invoker <NSObject>

-(id)invokeWithSelector:(SEL)sel args:(NSArray *)args ;

@end

NS_ASSUME_NONNULL_END

声明:iOS的动态代理,这里是手动实现的,逻辑并不完善。
java中有自己的动态代理,对这套代码无需看懂。
简单叙述下,就是通过消息转发将方法调用转发给其他的对象(realSubject)来处理。

#import "Proxy.h"
#import <objc/message.h>

static id<Invoker> proxyHander;

static id localProtol ;

@implementation Proxy

+(id)proxyInstance:(Protocol *)protocol handler:(id<Invoker>)handler {
    
    localProtol = protocol;
    proxyHander = handler ;
    class_addProtocol(self, protocol);
    return [[Proxy alloc] init];
}


+(id<Invoker>)getProxyHandler {
    
    return proxyHander;
}

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    
    return [[proxyHander class] instanceMethodSignatureForSelector:@selector(invokeWithSelector:args:)];
}

-(void)forwardInvocation:(NSInvocation *)anInvocation {
    
    NSMethodSignature *signature = [[localProtol class] instanceMethodSignatureForSelector:anInvocation.selector];
    NSInteger count = signature.numberOfArguments;
   //说明 所有参数必须atIndex: >=2 因为前面默认两个参数
    SEL selector = anInvocation.selector;
    NSMutableArray * args = [NSMutableArray array];
    for (int i = 0; i < count - 2; i ++) {
        const char * type = [signature getArgumentTypeAtIndex:2];
        NSString * car = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
        if ([car isEqualToString:@":"]) {
            SEL selector ;
            [anInvocation getArgument:&selector atIndex:i + 2];
            [args addObject:NSStringFromSelector(selector)];
        }else if([car isEqualToString:@"@"]){
            NSObject * obj ;
            [anInvocation getArgument:&obj atIndex:i + 2];
            [args addObject:obj];
        }else if([car isEqualToString:@"i"]){
            int num;
            [anInvocation getArgument:&num atIndex:i + 2];
            [args addObject:@(num)];
        }else if([car isEqualToString:@"B"]){
            int num;
            [anInvocation getArgument:&num atIndex:i + 2];
            [args addObject:@(num)];
        }else if([car isEqualToString:@"f"]){
            float num;
            [anInvocation getArgument:&num atIndex:i + 2];
            [args addObject:@(num)];
        }
    }
    [anInvocation setArgument:&selector atIndex:2];
    if (args) {
        [anInvocation setArgument:&args atIndex:3];
    }
    [anInvocation setTarget:proxyHander];
    [anInvocation setSelector:@selector(invokeWithSelector:args:)];
    [anInvocation invokeWithTarget:proxyHander];
}

@end

处理自己的数据的代理
iOS可以用继承,也可以使用拓展,这里用的扩展

#import "Proxy+OwnnerProxy.h"
#import "LocalUser.h"
#import "NewOwnerInfoHandler.h"


@implementation Proxy (OwnnerProxy)

-(id<UserInfo>)getOwnerWithObject:(id<UserInfo>)object {
    
    NewOwnerInfoHandler * hander = [[NewOwnerInfoHandler alloc] initWithUser:object];
    return [Proxy proxyInstance:object handler:hander];
}

处理别人数据的代理

#import "Proxy+OtherProxy.h"
#import "NewOtherInfoHandler.h"

@implementation Proxy (OtherProxy)

-(id)getOtherWithObject:(id)object {
    
    NewOtherInfoHandler * hander = [[NewOtherInfoHandler alloc] initWithUser:object];
    return [Proxy proxyInstance:object handler:hander];
}

@end

处理自己信息的RealSubject

#import "NewOwnerInfoHandler.h"
#import <objc/message.h>

@interface NewOwnerInfoHandler()

@property (nonatomic ,strong)id<UserInfo> localUser;

@end

@implementation NewOwnerInfoHandler

- (instancetype)initWithUser:(id<UserInfo>)localUser {
    if (self = [super init]) {
        
        _localUser = localUser;
    }return self;
}

-(id)invokeWithSelector:(SEL)sel args:(NSArray *)args{
    
    NSString * selectorName = NSStringFromSelector(sel) ;
    NSMethodSignature *signature = [[_localUser class] instanceMethodSignatureForSelector:sel];
    if([selectorName isEqualToString:@"setGrade:"]) {
        @throw [NSException exceptionWithName:@"error opration" reason:@"you can`t change your grade" userInfo:nil];
    }else if ([selectorName hasPrefix:@"set"]) {//set方法
        for (NSObject *obj in args) {
            if ([obj isKindOfClass:[NSNumber class]]) {
                const char * type = [signature getArgumentTypeAtIndex:2];
                NSString * car = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
                if([car isEqualToString:@"B"]) {
                    objc_msgSend(_localUser, sel,[[args firstObject] intValue]);
                }else if([car isEqualToString:@"f"]) {
                    objc_msgSend(_localUser, sel,[[args firstObject] intValue]);
                }else if([car isEqualToString:@"f"]) {
                    objc_msgSend(_localUser, sel,[[args firstObject] intValue]);
                }else {
                     objc_msgSend(_localUser, sel,[[args firstObject] intValue]);
                }
            }else {
                objc_msgSend(_localUser, sel,[args firstObject]);
            }
        }
    }else {//普通方法和get方法
        return objc_msgSend(_localUser, sel);
    }
    return nil;
}
@end

处理别人信息的realSubject

#import "NewOtherInfoHandler.h"
#import <objc/message.h>
@interface NewOtherInfoHandler()

@property (nonatomic ,strong)id<UserInfo> localUser;

@end
@implementation NewOtherInfoHandler

- (instancetype)initWithUser:(id<UserInfo>)localUser {
    if (self = [super init]) {
        
        _localUser = localUser;
    }return self;
}

-(id)invokeWithSelector:(SEL)sel args:(NSArray *)args{
    
    NSString * selectorName = NSStringFromSelector(sel) ;
    if([selectorName isEqualToString:@"setGrade:"]) {
        
        objc_msgSend(_localUser, sel,[[args firstObject] integerValue]);
    }else if ([selectorName hasPrefix:@"set"]) {//set方法
        
        @throw [NSException exceptionWithName:@"error opration" reason:@"you have no right to modify some one`s infomation" userInfo:nil];
    }else {//普通方法和get方法
        return objc_msgSend(_localUser, sel);
    }
    return nil;
}

@end

动态代理调用
这里数据直接造一个假设是从数据库中拿出来的。

#import <Foundation/Foundation.h>
#import "Proxy+OwnnerProxy.h"
#import "LocalUser.h"
#import "Proxy+OtherProxy.h"

int main(int argc, const char * argv[]) {
    
    @autoreleasepool {
        // insert code here...
        //制造的数据
        Proxy * proxy = [[Proxy alloc] init];
        LocalUser * local = [[LocalUser alloc] init];
        local.name = @"汪撕葱";
        local.age = 28;
        local.sex = 0;
        local.hobit = @"谈朋友";
        local.publicExperice = @"谈过100个女朋友";
        local.grade = 9;
        //开始动态代理
//        LocalUser * user = [proxy getOwnerWithObject:local];
//        user.grade = 10;
        
        LocalUser * user = [proxy getOtherWithObject:local];
        user.grade = 10;
        
        user.name = @"华克";
    }
    return 0;
}

优点

只需要关注业务逻辑本身,保证业务逻辑的重用性
在一定程度上解耦

缺点

可能导致速度变慢
实现代理模式需要额外的工作,有些代理模式的实现非常的复杂

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,179评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,229评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,032评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,533评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,531评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,539评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,916评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,813评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,568评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,654评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,354评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,918评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,152评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,852评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,378评论 2 342