【原】谈谈对Objective-C中代理模式的误解

本文转载请注明出处 —— polobymulberry-博客园

1. 前言


这篇文章主要是对代理模式和委托模式进行了对比,个人认为Objective-C中的delegate大部分用法属于委托模式。全文有些抠概念,对实际开发没有任何影响。

前段时间看到的一篇博客iOS开发——从一道题看Delegate,和这篇博客iOS APP 架构漫谈解决的问题类似。两篇blog都写得很不错,都是为了解决两个页面之间的数据传递问题:

A页面中有一个UILabel *labelA,B页面中有一个UITextField *textFieldB。从A页面跳转到B页面后,更改textFieldB中数据再返回到A页面,labelA显示的将是textFieldB中更改后的数据,嗯,就是这么简单的一个数据传递场景。

解决这个问题方法很多,比如使用一个DAO(data access object)去维护labelA和textFieldB所对应的数据。页面的数据流向如下图这样:

但是这个场景不是很复杂,所以并不需要引入DAO这么重的架构。

有时候我们会陷入技术的细节不可自拔,不妨静下来想一想,这个问题本质在什么?

这个问题的难点在于页面B中textFieldB的数据变化后无法通知页面A中的labelA。如果页面B中有labelA的引用就好了,这样就可以直接在页面B的代码中操作labelA。于是我在页面B中添加了一个UILabel *labelARef,在A页面push到B页面时,将页面A的labelA赋值给labelRef即可(亲测可以进行数据传递)。

上述方法确实可行,不过大家肯定都觉得这样设计也是太粗暴了。如果数据传递的业务比较多,那么页面B中就需要引用很多页面A的属性。当然我们可以直接引用页面A作为页面B的属性,即UIViewController *vcA。如下图所示:

这样设计其实没啥问题。不过我们这次主题是代理模式,那我们说的这个问题到底和代理模式有什么联系呢?

2.使用代理模式实现数据传递


我们先看看GoF《设计模式:可复用面向软件的基础》中对代理模式的描述:为其他对象提供一种代理以控制对这个对象的访问。咦,是不是和上面这个问题很像?为页面B提供一种代理以控制页面A的访问,能控制页面A,那就能控制页面A中的labelA。可是上面那种直接引用对象的方法也可以提供对这个对象的访问啊,为什么一定要通过代理呢?我们来看下代理模式的UML图:

注意上图中Proxy和RealSubject都实现了Subject这个接口,并且实现了相同的接口函数DoAction(),另外Proxy存有一份RealSubject的引用,即图中的delegate。一般来说,Proxy在实现DoAction时,会调用RealSubject的DoAction,也就是利用所引用的delegate调用RealSubject的DoAction。按照我自己的理解,之所以会出现代理模式,是由于用户需要对RealSubject的DoAction功能进行扩展,又无法对RealSubject中的DoAction直接进行修改(而且也违反了封闭-开放原则),于是使用了Proxy对RealSubject的DoAction进行了扩展,而扩展的内容都是DoAction,所以又将DoAction抽象出来,做成了接口。

回到上面那个案例,我们可以利用代理模式进行如下架构设计:

这里介绍一个小技巧,即如何辨别谁是代理 —— 直接跟Client打交道的是代理,此处Client就是ViewControllerB的textFieldB控件,所以直接打交道的就是ViewControllerB,也就是说ViewControllerB是代理。

代码如下:

// DataTransDelegate

  1. // DataTransDelegate
  2. @protocol DataTransDelegate <NSObject>
  3. - (void)didTextFieldChanged:(UITextField *)textField;
  4. @end

// ViewControllerA

  1. // ViewControllerA.m
  2. #import "ViewControllerA.h"
  3. #import "ViewControllerB.h"
  4. #import "DataTransDelegate.h"
  5.  
  6. @interface ViewControllerA () <DataTransDelegate>
  7. @property (strong, nonatomic) UILabel *labelA;
  8. @property (strong, nonatomic) UIButton *buttonA;
  9. @end
  10.  
  11. @implementation ViewControllerA
  12.  
  13. - (void)viewDidLoad {
  14. [super viewDidLoad];
  15.  
  16. [self.view addSubview:self.labelA];
  17. [self.view addSubview:self.buttonA];
  18.  
  19. [self.buttonA addTarget:self action:@selector(pushVC) forControlEvents:UIControlEventTouchUpInside];
  20. }
  21.  
  22. - (void)pushVC
  23. {
  24. ViewControllerB *vcB = [[ViewControllerB alloc] init];
  25. vcB.delegate = self;
  26. [self.navigationController pushViewController:vcB animated:NO];
  27. }
  28.  
  29. - (void)didTextFieldChanged:(UITextField *)textField
  30. {
  31. self.labelA.text = textField.text;
  32. }
  33.  
  34. - (UILabel *)labelA
  35. {
  36. if (_labelA == nil) {
  37. _labelA = [[UILabel alloc] initWithFrame:CGRectMake(, , , )];
  38. _labelA.text = @"显示vcB中的textField内容";
  39. }
  40. return _labelA;
  41. }
  42.  
  43. - (UIButton *)buttonA
  44. {
  45. if (_buttonA == nil) {
  46. _buttonA = [[UIButton alloc] initWithFrame:CGRectMake(, , , )];
  47. _buttonA.backgroundColor = [UIColor blueColor];
  48. [_buttonA setTitle:@"进入vcB" forState:UIControlStateNormal];
  49. }
  50. return _buttonA;
  51. }
  52.  
  53. @end

// ViewControllerB

  1. // ViewControllerB.h
  2. @protocol DataTransDelegate;
  3.  
  4. @interface ViewControllerB : UIViewController
  5. @property (nonatomic, weak) id<DataTransDelegate> delegate;
  6. @end
  7.  
  8. // ViewController.m
  9. #import "ViewControllerB.h"
  10. #import "DataTransDelegate.h"
  11.  
  12. @interface ViewControllerB () <UITextFieldDelegate, DataTransDelegate>
  13. @property (strong, nonatomic) UITextField *textFieldB;
  14. @end
  15.  
  16. @implementation ViewControllerB
  17.  
  18. - (void)viewDidLoad {
  19. [super viewDidLoad];
  20.  
  21. [self.view addSubview:self.textFieldB];
  22. self.textFieldB.delegate = self;
  23. }
  24.  
  25. - (void)textFieldDidEndEditing:(UITextField *)textField
  26. {
  27. [self didTextFieldChanged:textField];
  28. }
  29.  
  30. - (void)didTextFieldChanged:(UITextField *)textField
  31. {
  32. [self.delegate didTextFieldChanged:textField];
  33. }
  34.  
  35. - (UITextField *)textFieldB
  36. {
  37. if (_textFieldB == nil) {
  38. _textFieldB = [[UITextField alloc] initWithFrame:CGRectMake(, , , )];
  39. _textFieldB.text = @"输入文字";
  40. _textFieldB.backgroundColor = [UIColor redColor];
  41. }
  42. return _textFieldB;
  43. }
  44.  
  45. @end

效果如下:

3.关于代理模式误解


其实到目前为止并没有什么异样。关键是在大家对Objective-C的protocol使用上,一般是结合delegate使用的。大多数我们称这种模式是代理模式,但是我觉得delegate更像是一种委托模式,而非真正意义上的代理,代理是proxy,而委托是delegate。另外,代理模式中代理和被代理者都需要继承并实现同一个接口Subject,而我们使用delegate一般只需要让其中一个类继承并实现对应接口即可。

委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。其实上面的viewControllerB包含了viewControllerA的引用这种做法就是委托模式。

比如我们最为熟知的UITableView,就是一个典型的委托模式,它将tableView的中不变的部分封装起来,将经常变化的部分委托给用户自己处理,所以说UITableView就是一个delegator,而遵循UITableViewDelegate的那个类就是delegate,所以我们经常会在一个UIViewController中使用类似self.tableView.delegate = self这样的表达;

大家可能会疑惑为什么还需要使用UITableViewDelegate这种类似于Java中的interface?我个人理解是因为这样方便统一接口,接口统一了,方便了用户,因为只需要实现这几个接口就可以了。

所以我们可以看到最开始提到的两篇博客其实借助了Objective-C中的protocol实现了的其实是委托模式。

如果非要说委托模式和代理模式什么关系的话,我觉得代理模式应该算是一种特殊的委托模式。

【原】谈谈对Objective-C中代理模式的误解的更多相关文章

  1. JAVA中代理模式

    代理模式 在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用.代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能 ...

  2. 谈谈Java中的代理模式

    首先来看一下代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问.在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用, 其特征是代理类与 ...

  3. 代理模式与Android

    代理模式(Proxy) 一.   什么是代理模式 先来看看官方的说法,代理模式就是为其它对象提供一种代理,以控制对这个对象的訪问. 看来这个官方的说法的确有点官方,看了还是让人感觉不点不知所措,还是不 ...

  4. Java静态代理与动态代理模式的实现

    前言:    在现实生活中,考虑以下的场景:小王打算要去租房,他相中了一个房子,准备去找房东洽谈相关事宜.但是房东他很忙,平时上班没时间,总找不到时间去找他,他也没办法.后来,房东想了一个办法,他找到 ...

  5. C++设计模式之代理模式

        IPhone 6已经在中国香港开售了,圆了在专卖店等候一个多月苹果粉丝的苹果梦.然而对中国大陆而言.须要到9月17日苹果才在大陆开售.这对中国大陆的粉丝而言,不亚于一种煎熬,因此而滋生一种代购 ...

  6. java 代理模式-静态代理与动态代理

    最近在研究SpringAOP,当然要学习AOP就要知道这么健硕.强大的功能的背后究竟隐藏着怎样不可告人的“秘密”?? 接下来就是查阅了许多资料详细的研究了一下Java的代理模式,感觉还是非常非常重要的 ...

  7. 从静态代理,jdk动态代理到cglib动态代理-一文搞懂代理模式

    从代理模式到动态代理 代理模式是一种理论上非常简单,但是各种地方的实现往往却非常复杂.本文将从代理模式的基本概念出发,探讨代理模式在java领域的应用与实现.读完本文你将get到以下几点: 为什么需要 ...

  8. C#设计模式之十三代理模式(Proxy)【结构型】

    一.引言   今天我们要讲[结构型]设计模式的第七个模式,也是"结构型"设计模式中的最后一个模式,该模式是[代理模式],英文名称是:Proxy Pattern.还是老套路,先从名字 ...

  9. C#设计模式之十二代理模式(Proxy Pattern)【结构型】

    一.引言 今天我们要讲[结构型]设计模式的第七个模式,也是“结构型”设计模式中的最后一个模式,该模式是[代理模式],英文名称是:Proxy Pattern.还是老套路,先从名字上来看看.“代理”可以理 ...

随机推荐

  1. mapreduce多文件输出的两方法

    mapreduce多文件输出的两方法   package duogemap;   import java.io.IOException;   import org.apache.hadoop.conf ...

  2. ExtJS 4.2 评分组件

    上一文章是扩展ExtJS自带的Date组件.在这里将创建一个评分组件. 目录 1. 介绍 2. 示例 3. 资源下载 1. 介绍 代码参考的是 Sencha Touch 2上的一个RatingStar ...

  3. 一起来玩echarts系列(一)------箱线图的分析与绘制

    一.箱线图 Box-plot 箱线图一般被用作显示数据分散情况.具体是计算一组数据的中位数.25%分位数.75%分位数.上边界.下边界,来将数据从大到小排列,直观展示数据整体的分布情况. 大部分正常数 ...

  4. “.Net 社区虚拟大会”(dotnetConf) 2016 Day 2 Keynote: Miguel de Icaza

    美国时间 6月7日--9日,为期三天的微软.NET社区虚拟大会正式在 Channel9 上召开,美国时间6.8 是第二天, Miguel de Icaza 做Keynote,Miguel 在波士顿Xa ...

  5. 拨开迷雾,找回自我:DDD 应对具体业务场景,Domain Model 到底如何设计?

    写在前面 除了博文内容之外,和 netfocus 兄的讨论,也可以让你学到很多(至少我是这样),不要错过哦. 阅读目录: 迷雾森林 找回自我 开源地址 后记 毫无疑问,领域驱动设计的核心是领域模型,领 ...

  6. HTML5 input元素新的特性

    在HTML5中,<input>元素增加了许多新的属性.方法及控件.本文章分别对这三方面进行介绍. 目录 1. 属性 2. 方法 3. 新控件 1. 属性 <input>元素在H ...

  7. Android权限管理之Permission权限机制及使用

    前言: 最近突然喜欢上一句诗:"宠辱不惊,看庭前花开花落:去留无意,望天空云卷云舒." 哈哈~,这个和今天的主题无关,最近只要不学习总觉得生活中少了点什么,所以想着围绕着最近面试过 ...

  8. IOS FMDB 获取数据库表和表中的数据

    ios开发中,经常会用到数据库sqlite的知识,除了增,删,改,查之外,我们说说如何获取数据库中有多少表和表相关的内容. 前言 跟数据库使用相关的一般的增删改查的语句,这里就不做解释了.在网上有很多 ...

  9. 原生js+css3实现图片自动切换,图片轮播

    运用CSS3transition及opacity属性 制作图片轮播动画 自己这两天根据用js来控制触发CSS3中transition属性,从而写出来的以CSS3动画为基础,js控制过程的图片轮播 运用 ...

  10. pt-heartbeat

    pt-heartbeat是用来监测主从延迟的情况的,众所周知,传统的通过show slave status\G命令中的Seconds_Behind_Master值来判断主从延迟并不靠谱. pt-hea ...