zoukankan      html  css  js  c++  java
  • iOS设计模式之:建造者模式Builder Pattern,用于改进初始化参数

    转自:http://www.cnblogs.com/wengzilin/p/4365855.html

    本文主要讨论一下iOS中的Builder Pattern。与网上很多版本不同,本文不去长篇大论地解释建造者模式的概念,那些东西太虚了。设计模式这种东西是为了解决实际问题的,不能为了设计模式而设计模式,虽然这句话有点拗口!我希望我们都能宏观地看待某个设计模式,不必去太可以追求概念上的东西。事实上,只要你懂得如何应用,那此模式彼模式叫什么名称已经无所谓了。

    我们先来看个例子,假设你现在要买一辆车,提出以下一堆要求:白色、价格10万以内、必须是国产车(爱国是必须的)、5座...,用iOS代码描述就是这样的:

    1         WZLCar *myCar = [[WZLCar alloc] init];
    2         myCar.color = [UIColor whiteColor];
    3         myCar.price = 100000;
    4         myCar.family = @"China";
    5         myCar.seatCount = 5;
    6         //...more properties

    或者是这样:

    1 WZLCar *myCar = [WZLCar alloc] initWithColor:[UIColor whiteColor] price:100000 family:@"China" seatCount:5 ....];

    实际上,以上两种方式分别代表了两种iOS对象的初始化方式。在未使用Builder Pattern之前,这两种初始化对象方式都有不方便的地方。第一种方法灵活,但是如果你接触iOS时间长点就会发现,经常会记不住这个类到底有哪些property需要初始化,尤其是当这个类是被人提供给你的时候!我们多么希望类的提供者能搞点注释啊啥的告知我们一下。第二种方法则很清晰地告诉调用者到底有哪些property需要在创建对象时初始化。但是,当类的提供者有天跟你说:hey,哥们,现在业务逻辑改变了,我增加了一个xxx属性,init方法也变了。这时,你是不是很想打他?每新增一个新的属性,init方法就要变动,而且当需要初始化的属性多大十几二十个时,这个init方法要不要太壮观!

    所以,当某个类的属性值很多时,我们可以考虑使用建造者模式Builder Pattern来让初始化过程清晰一些,类的使用者会很感恩你这么做的。用iOS描述大概是这样的:

    1 WZLCarBuilder *builder = [[WZLCarBuilder alloc] init];//builder里面列出了所有需要初始化的参数,可以认为是一个to-do list
    2 builder.color=[UIColor whiteColor];
    3 builder.price=100000;
    4 builder.family=@"China";
    5 builder.seatCount=5;
    6 WZLCar *car=[builder build];//build方法产生一个WZLCar实例

    其中WZLCarBuilder类的property将WZLCar类中需要初始化的property复制了一遍。当你在实例化一个WZLCar对象而忘记那些参数时,可以跳转到WZLCarBuilder类的头文件看一下就一目了然了。从这种角度看,builder其实就是一个to-do list供类的调用者查阅。有强迫症的同学可以对以上代码并不感冒,因为它显得不够紧凑不够优雅,在初始化一个WZLCar对象前要搞一堆代码。有强迫症的coder大部分都是好程序员,那我们就尝试让它更优雅一些。我们可以这么做:

    1         WZLCar *myCar = [WZLCar creatWithBuilder:^(WZLCarBulider *builder){
    2             builder.color = [UIColor whiteColor];
    3             builder.price = 50000;
    4             builder.family = @"China";
    5             builder.seatCount = 4;
    6         }];
    7         NSLog(@"myCar:%@", [myCar description]);

    将builder的配置信息封装到block中,这样代码整体看起来紧凑很多,也给类的调用者很多提示信息。WZLCar的 + (WZLCar *)creatWithBuilder:(WZLCarBuliderBlock)block 方法实现如下,详见代码注释部分:

    1 + (WZLCar *)creatWithBuilder:(WZLCarBuliderBlock)block
    2 {
    3     NSParameterAssert(block != nil);//参数单元测试是必须的
    4     WZLCarBulider *bulider = [[WZLCarBulider alloc] init];
    5     block(bulider);//这里的builder是一个指针,block内部对其所作的改变都会被保留下来
    6     return [bulider build];//build创建一个WZLCar实例
    7 }

    WZLCarBuliderBlock是一个block声明: 

    typedef void (^WZLCarBuliderBlock) (WZLCarBulider *bulder);

    剩下的工作就是对WZLCarBuilder的build方法实现了:

     1 - (WZLCar *)build
     2 {
     3     NSAssert(self.color, @"color property is forcely to be initilized!");
     4     WZLCar *car = [[WZLCar alloc] init];
     5     car.color = self.color;
     6     car.price = self.price;
     7     car.family = self.family;
     8     car.seatCount = self.seatCount;
     9     return [car autorelease];
    10 }

    一些需要调用者强制初始化的参数可以build函数的开头处添加断言,一旦WZLCar类的调用者在初始化时没有初始化color就是断言失败抛出异常。这在多人协同开发时可以省事很多。下面我们像文章开头一般,调用一下WZLCar类:

    1 WZLCar *myCar = [WZLCar creatWithBuilder:^(WZLCarBulider *builder){
    2     builder.color = [UIColor whiteColor];
    3     builder.price = 50000;
    4     builder.family = @"China";
    5     builder.seatCount = 4;
    6 }];
    7 NSLog(@"myCar:%@", [myCar description]);

    Log确实打印出相应的信息,我就截图了,这就说明builder pattern确实让WZLCar的初始化产生效果。按照上面的代码,如果WZLCar类的使用者一时疏忽忘记初始化color属性,程序会在断言处抛异常:

    WZLCar *myCar = [WZLCar creatWithBuilder:^(WZLCarBulider *builder){
        //builder.color = [UIColor whiteColor];
        builder.price = 50000;
        builder.family = @"China";
        builder.seatCount = 4;
    }];
    NSLog(@"myCar:%@", [myCar description]);

    ===================================

    在FaceBook的开源动画框架POP中也有对builder pattern类似的应用:

    1
    2
    3
    4
    5
    6
    POPAnimatableProperty *animatableProperty = [POPAnimatableProperty propertyWithName:@"property" initializer:^(POPMutableAnimatableProperty *prop) {
        prop.writeBlock = ^(id obj, const CGFloat values[]) {
        };
        prop.readBlock = ^(id obj, CGFloat values[]) {
        };
    }];

    这里的initializer本质上就是builder,只是叫法不同而已。

    PS:从本文的builder pattern实现中你也可以抽象出一些东西应用在其他方面,比如利用block来封装一些属性的配置信息,比如iOS8中新增加的UIAlertController类就用到这种思想,是用block让调用者在一个block内给出对UITextField的配置:

    ##THAT IS ALL.

    =======================================

    原创文章,转载请注明 编程小翁@博客园,邮件zilin_weng@163.com,微信Jilon,欢迎各位与我在C/C++/Objective-C/机器视觉等领域展开交流!

     =======================================

  • 相关阅读:
    利用腾讯云COS云对象存储定时远程备份网站
    怎么生成主机最快
    海量服务实践──手 Q 游戏春节红包项目设计与总结(下篇)
    零基础读懂视频播放器控制原理——ffplay播放器源代码分析
    海量服务实践──手 Q 游戏春节红包项目设计与总结(上篇)
    Express+Less+Gulp配置高效率开发环境
    告知你不为人知的UDP-疑难杂症和使用
    告知你不为人知的UDP-连接性和负载均衡
    国内首款 FPGA 云服务器,性能是通用 CPU 服务器 30 倍以上
    Nginx反向代理腾讯云COS的一个坑
  • 原文地址:https://www.cnblogs.com/feiyu-mdm/p/5579514.html
Copyright © 2011-2022 走看看