由于最经工作较为清闲,就开始自学了ios开发,来实现我今年自己对自己的承诺。然而在ios控件这一节的学习的当中,突然发现:android中监听控件的点击、长按、触摸、拖动等事件的监听器设计模式。在ios开发中竟然是使用代理模式来设计的。

这里先补一句:在观察者模式讲解一章当中曾经提到过Button的点击按钮事件处理。这里容易把观察者模式和监听器模式弄混淆,其实这里很容易区分开。因为Button的事件处理主要分为两块,同时也使用了两种设计模式

  1. 设置/添加事件以及当按钮状态发生变化(何为变化:简而言之你人正在与按钮交互,而导致了按钮的状态发生了改变)后。通知他的倾听者—观察者模式
  2. 倾听者知道了按钮状态的发生了变化后。再根据不同的状态去回调之前设置的监听器中的回调函数—监听器模式
    其实按钮被点击到执行了之前设置的监听器中的回调函数这一流程远远不止使用了这两种设计模式。当然这里我们不做过多的讨论

android开发当中,我们使用监听器模式,通过回调函数,将我们需要的参数自内而外的传递出去。而ios开发中使用的代理模式却有着异曲同工之妙。当然也有着本质区别。这里只是做个比较来记录和区别这两种模式。
( PS:貌似监听器模式不在二十三种设计模式之中。)



1、监听器模式(android/java)

这里有一个实际的开发场景:
一个下载组件从网络下载文件完成后我们需要去做一些事情善后:比如关闭进度条、关闭输入输出流、告诉另外一个组件:“哥东西拿到手了,给你····”巴拉巴拉等等…
这里有一个问题就是:我们都知道这些善后的工作是要在下载完毕的那一刻去做,但是我们怎么知道什么时候下载完毕?总不能另起一线程,3秒一循环的去问下载组件:“哥们,事情搞完没?”、哥们,进度条怎么还开启着在?”、哥们,文件拿到手了没?”。如果这样,我觉得你会死的很惨。
既然我们不知道什么时候下载完毕。那就换一条思路:先给下载组件的哥们一个手机,跟这哥们说:“哥们,麻烦您自己默默下载完了后,用这个手机给我打个电话。下次请你撸串…”这哥们一听说能撸串,打个电话多大事儿···打就打呗…思路就是这样。下面用代码来体现下载文件的Demo:

/**
 *下载器哥们
 */
public class Downloader {

    public void downloading(CallBack callBack) {
        //模拟下载后完成后的产物
        StringBuffer result = new StringBuffer();
        for (int i = 1; i <= 100; i++) {
            sleep();
            if (i % 20 == 0) {//模拟下载进度
                result.append(i + "...");
                System.out.println(String.format("已下载%d%", i));
            }
            if(i == 100){//当下载完成后就打电话说我已经下载完成了,并把下载的东西传递出去
                System.out.println("哥们,我已经下载完啦,东西给你");
                callBack.OnCompletion(result.toString());
            }
        }
    }

    private void sleep() {
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

在上面代码中下载方法downloading中有一个接口类型的对象,该对象可以理解为就是那个用来打电话告诉我下载完成的电话

/**
 *下载完成后打电话给我:下载完成啦...
 */
public interface CallBack {

    public void OnCompletion(String result);

}

最后是自己使用下载器组件的代码:

//实现回调函数,通俗讲就是:实现一个用来让下载器哥们在下载完成时候通知我的电话
public class Mine implements CallBack{

    public static void main(String[] args) {
        Mine test = new Mine();
        test.start();
    }

    public void start(){
        Downloader downloader = new Downloader();
        //让下载器哥们去下载,并且给他一个电话,用来在下载完成的时候通知我一声
        downloader.downloading(this);
    }

    @Override
    public void OnCompletion(String result) {
        System.out.println("谢谢啊,东西我已经拿到了:" + result);
    }

}

运行后控制台打印:

已下载20%
已下载40%
已下载60%
已下载80%
已下载100%
哥们,我已经下载完啦,东西给你
谢谢啊,东西我已经拿到了:20...40...60...80...100...

我们实现了CallBack接口,用来接收下载器给我们打的电话,并且我们也拿到了下载后的东东(这里不要纠结下载的东西,只是随便模拟。太懒了····)。我们关注的是:下载完成后,可以把数据从内而外的传递出去,并且也成功监听到了下载完成的时机 至于之后想做什么,那是自己的事儿了。

写到这儿,我只想说监听器模式其实真心很简单,在android中的使用场景比比皆是,我就不累赘了。。。



2、代理模式(ios/objective-c)

定义:代理模式为一个对象提供一个替身帮这个对象做事,同时也控制了对这个对象的访问进而封装性得到进一步提升
什么意思呢?打个比方:我准备晚上带妹子去看电影,但是我现在太忙,根本抽不出空去买票。我就想找个人去电影院买票,然后回来把票给我,我就可以开心和妹子一起去看电影了。代理模式要完成的事情就这么简单。
下面用代码来说明这个场景:

首先是自己的.h文件 .h文件声明了代理变量用来帮我去买票 ,并且也声明了自己买票和自己看定影的方法

#import <Foundation/Foundation.h>
#import "BuyTicketDelegate.h"

@interface Person : NSObject

//声明代理变量
@property(nonatomic, weak) id<BuyTicketDelegate> agent;
/**买票*/
- (void) buyTicket;
/**看电影*/
- (void) seeMoive;

@end

接着是自己的.m文件。在买票的时候我自己根本抽不出空去买票,就让我自己的代理去帮我买票

#import "Person.h"

@implementation Person

//当我们要买票的时候,就让代理帮我们去买票
- (void)buyTicket{
    [self.agent askPrice];
    [self.agent buyTicket];
}

- (void)seeMoive{
    NSLog(@"带着妹子看电侏罗纪公园3D");
}

@end

当然,帮我买票的代理也不能是随随便便的一个人,我总不能在大街上随便抓一个人说:去!帮我买两张电影票。这太不实际了。所以要做我的代理就必须要遵守一些协议,现在就创建买票代理的协议:假设买票必须要经过先询问票价然后购买电影票这两步骤,那么买票的代理就起码要会这个技能吧?所以协议如下

#import <Foundation/Foundation.h>

@protocol BuyTicketDelegate <NSObject>

/**询问价格*/
- (void) askPrice;
/**买票*/
- (void) buyTicket;

@end

既然有了协议,那么创建两个买票的代理,当然肯定要遵守上面创建的买票协议

@interface AgentOne : NSObject <BuyTicketDelegate>

@end

@implementation AgentOne

- (void)askPrice{
    NSLog(@"还有100张票,每张30rmb");
}

- (void)buyTicket{
    NSLog(@"购买2张票,共花去60rmb");
}

@end





@interface AgentTwo : NSObject <BuyTicketDelegate>
@end

@implementation AgentTwo

- (void)askPrice{
    NSLog(@"还剩下3张票,每张33rmb");
}

- (void)buyTicket{
    NSLog(@"买了2张票,共花去66rmb");
}

@end

最后我们测试一下好不好使:
我们先创建自己对象,然后创建代理一号,暂且任命自己的代理为代理一号,然后晚上约上佳人看电影

int main(int argc, const char * argv[]) {

    Person *p = [[Person alloc] init];

    AgentOne *agentOne = [[AgentOne alloc] init];

    p.agent = agentOne;

    [p buyTicket];
    [p seeMoive];

    return 0;
}

控制台打印

2015-07-03 16:45:45.656 Proxy[1213:303] 还有100张票,每张30rmb
2015-07-03 16:45:45.658 Proxy[1213:303] 购买2张票,共花去60rmb
2015-07-03 16:45:45.659 Proxy[1213:303] 带着妹子看电侏罗纪公园3D

现在假如代理一号要忙活着自己的妹子准备罢工不干了,那我们只好启用代理二号,我们只需要将我自己的代理变量指向代理二号对象:

AgentTwo *agentTwo = [[AgentTwo alloc] init];

    p.agent = agentTwo;

其余的代码可以完全不用改变,我们依旧不用亲自去买票还是能够约上妹子去看电影。工作,娱乐两不误。嘿嘿嘿~~~

以上即是代理模式最直观简单的体验了。其实我在想这算不算一种设计模式,竟然如此简单。不过简单不简单到不是重要的,我想:学习设计模式,最重要的是领悟、运用并能扩展设计模式中的思维方式。



3、监听器模式和代理模式在android和在ios当中的对比

上面扯了这么多,我们回过头来:为什么监听器模式和代理模式在android开发和ios开发中的有异曲同工之妙呢?
因为在对view的点击、长按、触摸、拖动等事件方法的处理,ios使用的是代理模式,而android是监听器模式。但是两者的运用思想几乎是一致的~!

android中以实现监听按钮的点击事件来举例:
(ps:有android开发经验的人都知道监听按钮的点击事件有四种写法,以下为了做一个对比,android监听按钮点击事件的代码统一采用类实现接口的方式去写)

1、创建监听点击事件的OnClickListener监听器接口并定义回调方法(View类中已创建)

/**
     * Interface definition for a callback to be invoked when a view is clicked.
     */
    public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }

2、声明监听器实例变量(View类中已声明在内部静态类中)

static class ListenerInfo {

        //....省略n个实例变量

        /**
         * Listener used to dispatch click events.
         * This field should be made private, so it is hidden from the SDK.
         * {@hide}
         */
        public OnClickListener mOnClickListener;

        //....省略n个实例变量
    }

    ListenerInfo mListenerInfo;

3、在我们需要监听的按钮对象所在的类中实现监听器接口并重写回调方法

public class MainActivity extends Activity implements OnClickListener{

    //...省略n行代码

    @Override
    public void onClick(View v) {
        //....
    }

}

4、当前需要监听哪个按钮的点击事件,就为这个按钮添加监听器(因为当前Activity已经实现了监听器接口,所以可以直接写this)

@Override
    protected void onCreate(Bundle savedInstanceState) {
        //...省略一些代码
        button.setOnClickListener(this);
    }

ios中也是以上四步。现在以监听UIScrollView的开始滚动事件来举例 详细描述就不累赘了,直接看代码做个比较吧··· 1、UIScrollViewDelegate源码

//定义协议(android:定义接口)
@protocol UIScrollViewDelegate<NSObject>

@optional
//定义代理需要调用的方法:当UIScrollView控件开始滚动时会被调用(android:定义接口中需要实现的回调方法)
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;                                               

//...省略n个其它代理方法的声明

@end

2、UIScrollView源码

NS_CLASS_AVAILABLE_IOS(2_0) @interface UIScrollView : UIView <NSCoding> {
  @package
    id                _delegate;
}

//...
//声明代理属性(android:声明接口类型的对象)
@property(nonatomic,assign) id<UIScrollViewDelegate>      delegate;                       // default nil. weak reference

//...
@end

3、看注释

//遵守代理协议
@interface ViewController ()<UIScrollViewDelegate>

//....

@end

@implementation ViewController

//实现协议中代理需要调用的方法。该方法在scrollview滚动时候执行(android:实现接口中需要实现的方法用于回调)
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
   //....
}

@end

4、看注释

//为需要代理的scrollView对象设置代理,当scrollView控件开始滚动的时候会让代理立即去调ViewController中遵守协议而重写的scrollViewDidScroll:方法(android:为这个按钮添加监听器...不明白就回到上面去看button.setOnClickListener(this))
- (void)viewDidLoad {
    [super viewDidLoad];

    //.......省略n行代码

    //指定代理为self
   [self.scrollView setDelegate:self];

    //.......省略n行代码
}

上面android和ios的比较,我相信其中的不同和相同之处一眼就能看出来。就不用我在罗里吧嗦了吧…嘿嘿…完工。搓炉石去~