几种编程思想在iOS中的实现:(一)链式编程

前言

作为一个iOS程序员基本上都应该接触过Masonry这个自动布局库。这个库能够帮助程序员极大程度的简化自动布局的代码。使用这个库让我感到惊叹的不是如何能够将较为复杂的传统自动布局写法精简到如此程度,而是精简后的代码的书写方式:

make.left.right.bottom.mas_equalTo(0.f);

这种写法在做到简化的同时,通过点(.)调用的方式,将代码连接成一行,大大增加了代码的可读性,这就是本篇要提到的链式编程。

关于链式编程具体是种什么编程思想,这种概念性的东西,请自行百度,这里不多做介绍,本篇主要是通过一个简单的例子来实现上述的链式编程。

举个例子

日常开发中肯定少不了要与数据库打交道,而数据库相信不少项目是使用的splite3以及将其封装的很好的FMDB。如果在开发中直接使用FMDB,那么免不了要手动写SQL语句。当然现在网上也有不少的DAO,可以让开发者无需关心怎么去写SQL,甚至不需要懂SQL的语法,通过对对象的操作就可以达到在数据库的增删改查。

这里我们不讨论这种DAO是怎么实现的,我们实现的是通过一个工具类来帮助我们生成对应的SQL。免去我们手写“SELECT” “WHERE” 等关键字麻烦,而且可以智能提示,避免写错。

#不要问为什么有好好的DAO不去用。这只是个例子,不要太在意细节。

分析

既然要智能提示,免去手动书写“SELECT”等词的话,很明显是将其抽成一个方法供调用者调用,其内部将关键字拼接好。那么这个方法应该是如下类似的:

- (NSString *)select:(NSArray *)columns
                from:(NSString *)tableName;

但是,SELECT的情况不止这一种,还有FROM,JOIN ON,GROUP BY,ORDER BY等各种操作,而且是可选的,那么就需要增加以下的一系列类似方法:

- (NSString *)select:(NSArray *)columns
                from:(NSString *)tableName
               where:(NSString *)where;
               
- (NSString *)select:(NSArray *)columns
                from:(NSString *)tableName
               where:(NSString *)where
             orderBy:(NSString *)orderBy;
...

很明显,要为每一种组合情况创建一个方法,这样做显得不太明智。因为:这里的例子,SQL查询语句的组合还算是有限的,目前确实可以穷举,把每种可能性列出来,添加相应的方法,但是,要是哪天SQL支持ORDER BY可以写到前面去,那不就呵呵了,又要在增加对应组合情况的方法。显然这么做是不明智的。

那么就换一个方向,将SELECT等每一步操作拆分成一个个单独的方法,这样后续需要接WHERE条件还是直接ORDER BY都可以根据具体业务自由调用,比较灵活,而且这样更加符合面向对象的思想(设计模式中的建造者模式)。

传统做法

假如我们的工具类叫SQLTool,那么拆分后的方法定义应该是以下类似的:

@interface SQLTool : NSObject

//用于保存拼接后的SQL
@property (nonatomic, strong, readonly) NSString *sql;

//为了能后续接着调用,所以返回值还是该对象
- (SQLTool *)select:(NSArray *)columns;              
- (SQLTool *)from:(NSString *)tableName;
- (SQLTool *)where:(NSString *)where;
- (SQLTool *)orderBy:(NSString *)orderBy;
...

@end

实现部分这里就省略了,无非就是将传进来的参数按照各自的format拼接到self.sql后面,再返回对象自己。

使用起来是这样的:

SQLTool *tool = [[SQLTool alloc] init];

NSString *testSQL1 = [[[tool select:nil] from:@"Table"] orderBy:@"Column DESC"].sql;
NSString *testSQL2 = [[[[tool select:nil] from:@"Table"] where:@"A = B"] orderBy:@"Column DESC"].sql;

拆分以后,使用起来确实是灵活了,这里 testSQL2 比 testSQL1 多了一个WHERE条件。当然还可以添加leftJoin,rightJoin等方法,使用的时候可以跟业务需求调用。但是仔细一看testSQL1看上去还可以,但是testSQL2就感觉有点臃肿了,要是再加上groupBy,或者多个Join操作,那么这种臃肿就更加明显了。而这种臃肿主要体现在方法调用时候的那一对“[]”上。就算是回车换行,头尾的括号还是太多了,各个方法之间的衔接也因为括号的存在,阅读性不是很强。

链式编程

如果采用链式编程的方式,那么参考下Masonry的实现,我们最终实现的目标是以下类似的:

SQLTool *tool = [[SQLTool alloc] init];
NSString *testSQL1 = tool.select(nil).from(@"Table").orderBy(@"Column DESC").sql;

这种方式明显看上去清爽许多,就算免不了调用的方法次数较多,使得整行代码较长,通过换行还是能保持一定的可阅读性。

那么我们来分析下这样的书写方式是怎么实现的。

首先,用点(.)的形式调用,那可以确定基本上是属性(虽然没有参数的方法也可以通过点(.)的形式调用,但是一般不推荐这么做,有兴趣的朋友可以查阅一些代码规范或者相关帖子的说明);然后,后面可以在括号里传参数,那么进一步确定,这个属性应该是一个block;最后,调完一次后,还可以继续调用,那么说明block的返回值应该还是这个对象。

那么以select方法为例,改造后的代码如下:

.h文件

//定义select的block
typedef SQLTool *(^Select)(NSArray<NSString *> *columns);
@property (nonatomic, strong, readonly) Select select;

.m文件

- (Select)select {
    return ^(NSArray<NSString *> *columns) {
        if (columns.count > 0) {
            self.sql = [NSString stringWithFormat:@"SELECT %@", [columns componentsJoinedByString:@","]];
        } else {
            self.sql = @"SELECT *";
        }
        //这里将自己返回出去
        return self;
    }
}

看上面代码应该很清楚了,想要实现from、join等操作,也是用类似的方式就可以了,这里就不一一贴出来了。

一些总结

1.什么时候使用链式编程?

从上面的例子还有Masonry可以看出,在面向一些过程化处理的时候(拼接SQL、给View加约束,都可以看成需要一步步完成的过程),需要将这些“过程”拆分,然后在“组合”这些“过程”的时候,就可以使用链式编程,使得代码更加清晰,增加阅读性。

2.链式编程的核心实现

实现链式编程的关键就是声明一个block的属性,而这个block返回值必须还是一个对象(根据业务需求不同,可以返回的是这个对象实例本身,也可以是这个类的另一个实例,更可以是另一个类的实例对象)。而block中内部的逻辑就是项目的业务逻辑(在这个例子中是拼接了SQL语句)。

#补充:如果在调用后不需要传递参数的话,只需要声明一个类型是类自己的属性,并重写getter方法,在里面做相应的操作就可以了。以Masonry为例:

make.left.right.bottom.mas_equalTo(0.f);

前面部分的left,right,bottom都没有传递参数,那么这部分的属性是不需要声明为block的,具体可以去查看Masonry源码。

留在最后的思考

通过这个例子,虽然是简单的实现了链式编程,但是还是有一些问题值得我们去思考的:

  • 在使用上跟Masonry还是有些区别的,为什么?
  • 就本篇举的例子而言,还有些不足:在实际操作中,SELECT关键字及后面的列之后,应该且只能是FROM,这是固定的,既不是WHERE,也不是ORDER BY,而现在的例子里,因为block返回值是对象本身,所以可能会出现这样的情况:
tool.select(nil).from(@"table").from(@"table").select(nil);

也就是说,不但同一个操作可以重复调用,而且会出现不符合顺序的调用。怎么样才能避免这种情况呢,这些问题会在后续的文章中讲解。

谢谢阅读!

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

推荐阅读更多精彩内容