zoukankan      html  css  js  c++  java
  • 【原】谈谈对Objective-C中代理模式的误解

    【原】谈谈对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所对应的数据。页面的数据流向如下图这样:

    image

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

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

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

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

    image

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

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


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

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

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

    image

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

    代码如下:

    // DataTransDelegate

    // DataTransDelegate
    @protocol DataTransDelegate <NSObject>
    - (void)didTextFieldChanged:(UITextField *)textField;
    @end

    // ViewControllerA

    // ViewControllerA.m
    #import "ViewControllerA.h"
    #import "ViewControllerB.h"
    #import "DataTransDelegate.h"
    
    @interface ViewControllerA () <DataTransDelegate>
    @property (strong, nonatomic) UILabel *labelA;
    @property (strong, nonatomic) UIButton *buttonA;
    @end
    
    @implementation ViewControllerA
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self.view addSubview:self.labelA];
        [self.view addSubview:self.buttonA];
        
        [self.buttonA addTarget:self action:@selector(pushVC) forControlEvents:UIControlEventTouchUpInside];
    }
    
    - (void)pushVC
    {
        ViewControllerB *vcB = [[ViewControllerB alloc] init];
        vcB.delegate = self;
        [self.navigationController pushViewController:vcB animated:NO];
    }
    
    - (void)didTextFieldChanged:(UITextField *)textField
    {
        self.labelA.text = textField.text;
    }
    
    - (UILabel *)labelA
    {
        if (_labelA == nil) {
            _labelA = [[UILabel alloc] initWithFrame:CGRectMake(100, 100, 200, 50)];
            _labelA.text = @"显示vcB中的textField内容";
        }
        return _labelA;
    }
    
    - (UIButton *)buttonA
    {
        if (_buttonA == nil) {
            _buttonA = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 50)];
            _buttonA.backgroundColor = [UIColor blueColor];
            [_buttonA setTitle:@"进入vcB" forState:UIControlStateNormal];
        }
        return _buttonA;
    }
    
    @end

    // ViewControllerB

    // ViewControllerB.h
    @protocol DataTransDelegate;
    
    @interface ViewControllerB : UIViewController
    @property (nonatomic, weak) id<DataTransDelegate> delegate;
    @end
    
    // ViewController.m
    #import "ViewControllerB.h"
    #import "DataTransDelegate.h"
    
    @interface ViewControllerB () <UITextFieldDelegate, DataTransDelegate>
    @property (strong, nonatomic) UITextField *textFieldB;
    @end
    
    @implementation ViewControllerB
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self.view addSubview:self.textFieldB];
        self.textFieldB.delegate = self;
    }
    
    - (void)textFieldDidEndEditing:(UITextField *)textField
    {
        [self didTextFieldChanged:textField];
    }
    
    - (void)didTextFieldChanged:(UITextField *)textField
    {
        [self.delegate didTextFieldChanged:textField];
    }
    
    - (UITextField *)textFieldB
    {
        if (_textFieldB == nil) {
            _textFieldB = [[UITextField alloc] initWithFrame:CGRectMake(100, 100, 100, 50)];
            _textFieldB.text = @"输入文字";
            _textFieldB.backgroundColor = [UIColor redColor];
        }
        return _textFieldB;
    }
    
    @end

    效果如下:

    1

    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实现了的其实是委托模式。

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

  • 相关阅读:
    samba 4.11 or newer version enable SMB1
    linux下使用mv将递归的文件从多个目录移动到一个目录中
    【转载】修改Windows下键盘按键对应功能的一些方案
    ACR122U读卡器在win7以上系统使用过程中的设置项
    恢复Chrome 78以上版本的地址栏https和www显示
    配置sshd的免密码登录
    1234
    MarkDown 语法记录
    语法测试
    解决 Linux grep 不高亮显示
  • 原文地址:https://www.cnblogs.com/polobymulberry/p/5559075.html
Copyright © 2011-2022 走看看