如何优化冗长的条件语句

前言

我不讨厌简短的 if else,但是对于很长并且负责的 if else 就极其感到不舒服了,代码不但看起来难懂不雅,
关键是维护起来也是一大坨,生怕弄错了之前的逻辑。

OO设计遵循SOLID(单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)原则,
使用这个原则去审视if/else,可能会发现很多问题,比如不符合单一原则,
它本身就像一团浆糊,融合了各种作料,黏糊糊的很不干净;
比如不符合开闭原则,每新增一种场景,就需要修改源文件增加一条分支语句,
业务逻辑复杂些若有1000种场景就得有1000个分支流,这种情况下代码不仅仅恶心问题了,效率上也存在很大问题。
由此可见,if/else虽然简单方便,但不恰当的使用会给编码代码带来非常痛苦的体验。
针对这种恶心的if/else分支,我们当然首先想到的去重构它--在不改变代码外部功能特征的前提下对代码内部逻辑进行调整和优化,

而且《重构》一书上有讲到这个问题。if...else, swith...case 是面向过程的代码,在面向对象的代码中应尽可能少地出现。

四个优化方向

【1】尽量少用 else 尽量多用 if reture 的语法方式。
【2】字典的逻辑对应转化作用。
【3】用多态替代条件语句
【4】策略模式,继承重写,抽象父类和统一的接口入口。

一、尽量少用 else 尽量多用 if reture 的语法方式

当一些条件语句难以让人看清他的目的时,

- (void)showName:(NSString *)name
{
    if (name != nil)
    {
        if (name.length > 0)
        {
            NSLog(@"showName");
        }
        else
        {
            NSLog(@"name.length is zero");
        }
    }
    else
    {
        NSLog(@"name is nil");
    }
}

我们可以用卫语句来使得主体逻辑更加清晰尽量不使用 else

- (void)showName:(NSString *)name
{
    if (name == nil){
        NSLog(@"name is nil");
        return;
    }
    if (name.length == 0){
        NSLog(@"name.length is zero");
        return;
    }
    NSLog(@"showName");
}

二、字典的逻辑对应转化作用

- (NSDictionary *)strategyDict{
    if (_strategyDict == nil) {
        _strategyDict = @{
                          @"day1" : [self invocationWithMethod:@selector(playBasketball:)],
                          @"day2" : [self invocationWithMethod:@selector(shopping:)],
                          @"day3" : [self invocationWithMethod:@selector(washClothes:)],
                          @"day4" : [self invocationWithMethod:@selector(playGames:)],
                          @"day5" : [self invocationWithMethod:@selector(sing:)]
             };
    }
    return _strategyDict;
}
你会发现字典(哈希表)map是很神奇一种数据结构,再多考虑一步:
1.map的value中保存的不再是基本数据类型,而是对象。
  这样一来,通过不同的key可以拿到不同的对象,如果这些对象的类都实现同一个接口,那么这就是一个加强版的策略模式,
  就是多态性的体现,传统的策略模式传入的是实现类的对象,而通过map加强,只需传入一个数字或字符串即可实现多态。

2.map的value中保存的是函数,通过不同的key(消息类型)可以拿到不同的响应处理函数,则可以实现消息机制或事件驱动。

三、 用多态替代条件语句

使用多态的场景

  • 当对象要根据不同的状态表现不同的行为时。
  • 当你需要在很多地方检查相同的条件时。

我们来看简单的一个例子:

Class Update {
    execute() {
        if (FLAG_i18n_ENABLE) {
            //DO A;
        } else {
            //DO B;
        }
    }
    render() {
        if (FLAG_i18n_ENABLE) {
            //render A;
        } else {
            //render B;
        }
    }
}

那么,如何用多态来重写上面的类呢?
我们可以分为两步来操作:
- 让 Update 成为抽象类,方法也抽象。
- 在子类中的覆盖方法实现条件语句的分支操作。

代码如下:

abstract class Update {
    abstract execute();
    abstract render();
}
class I18NUpdate extends Update {
    execute() {
        //Do A;
    }
    render() {
        //render A;
    }
}
class NonI18NUpdate extends Update {
    execute() {
        //Do B;
    }
    render() {
        //render B;
    }
}

测试方法:

void testExecuteDoA() {
    Update u = new I18NUpdate();
    u.execute();
    assertX();
}
void testExecuteDoB() {
    Update u = new NonI18NUpdate();
    u.execute();
    assertX();
}

用多态实现的类,通过继承抽象类,重写抽象方法的方式,避免使用了条件语句。
在测试的时候,不需要关心它的状态码,子类本身就已经承载了状态信息。
所以你可以看到,在测试的时候,代码非常的清晰易懂。

使用多态实现的类有两个好处:

  • 我们可以通过增加新的子类来添加新的行为,而且不会影响到原来的代码。
  • 不同的操作和概念在不同的类中,容易理解和阅读。

这个例子太简单,可以看这篇文章中的例子:使用state pattern替代if else,就会发现使用多态替代条件语句不但优雅化了,而且在复杂的情况下是必须要这样处理了。这是一种全新的解决需求扩展和提高项目可维护性的方法。

四、策略模式优化条件语句

策略模式的定义

也叫政策模式,定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。
策略模式使用的就是面向对象的继承和多态机制,由三个角色构成

1、Rescue封装角色
  也叫上下文角色,起承上启下的封装作用,屏蔽高层模块对策略、算法的直接访问、封装可能存在的变化。
2、Strategy抽象策略角色
  策略、算法家族的抽象,通过为接口,定义每个策略或算法必须具有的方法和属性。
3、ConcreteStrategy具体策略角色
    实现抽象策略中的操作,该类含有具体的算法。
首先建立一个通用的策略,这里直接命名为Strategy。这个类是最终暴露出来,需要被调用的类。
//Strategy.h
#import <Foundation/Foundation.h>
@interface Strategy : NSObject
- (void) go;
@end

这个类比较简单,只定义了一个go方法。
//Strategy.m
#import "Strategy.h"
@implementation Strategy
- (void)go{
    NSLog(@"I am going outside");
}
@end

接下来定义两个类,分别继承自Strategy类,这两个类中包含了具体的方法实现,是功能的主体部分。

//OldPeopleTravel.h
#import <Foundation/Foundation.h>
#import "Strategy.h"
@interface OldPeopleTravel : Strategy
- (void) go;
@end
.m文件里是具体的针对对老年人的实现方法

//OldPeopleTravel.h
#import "OldPeopleTravel.h"
@implementation OldPeopleTravel

-(void)go{
    [super go];
    NSLog(@"I am old, I need rest");
}

@end

类似的还有YoungPeopleTravel的.h和.m文件

//YoungPeopleTravel.h
#import "Strategy.h"

@interface YoungPeopleTravel : Strategy
- (void) go;
@end

//YoungPeopleTravel.m
#import "YoungPeopleTravel.h"
@implementation YoungPeopleTravel
- (void) go{
    [super go];
    NSLog(@"I am young, I am energetic");
}
@end

以上是策略类和具体的实现类的实现,接下来就是调用这个策略了。

//ViewController.m
#import "ViewController.h"
#import "Strategy.h"
#import "OldPeopleTravel.h"
#import "YoungPeopleTravel.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self goOutside:[[OldPeopleTravel alloc]init]];
    [self goOutside:[[YoungPeopleTravel alloc]init]];
}

- (void)goOutside:(id)theStrategy{
    Strategy *strategy = theStrategy;
    [strategy go];
}

@end
可以看到,最终我们调用的是自己的goOutside方法,方法中有一个参数是strategy,
通过传入不同的参数(策略),就可以调用这个策略下具体的方法实现。运行结果表示策略模式已经成功的实现了。
通过调用不同的策略,得到了不同的处理结果。

多态和策略模式之间的联系

我们看完上面的第三中方法(用多态替代条件语句) 和 第四种方法(策略模式优化条件语句)没有感觉两者很相似,其实两者的侧重点不同。

多态性的定义是:同一操作作用于不同的类的实例,将产生不同的执行结果,即不同类的对象收到相同的消息时,得到不同的结果。多态是面向对象程序设计的重要特征之一,是扩展性在“继承”之后的又一重大表现 。对象根据所接受的消息而做出动作,同样的消息被不同的对象接受时可能导致完全不同的行为,这种现象称为多态性。

【1】首先多态是高层次,高度抽象的概念,独立于语言之外,是面向对象思想的精髓,而策略模式只是一种软件设计模式,相对而言更加具体
【2】其次,多态更多强调的是,不同的对象调用同一个方法会得到不同的结果,而策略模式更多强调的是,同一个对象(事实上这个对象本身并不重要)在不同情况下执行不同的方法,而他们的实现方式又是高度类似的,即共享同一个抽象父类并且各自重写父类的方法。
【3】策略模式是通过多态来实现不同子类的选取,是多态调用具体算法的展现。

总结

条件语句的优化,不是上述一种方式可以完成的,往往是上述几种方法的结合使用。

参考文章:

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

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • 乘一辆短途列车 遗憾 就成了未完待续的情节 你和我 都为此填补进 动人的言语 颠簸的表情 风,晚霞,恰到好处的海棠...
    2020号阅读 713评论 45 44
  • 看完琅琊榜,迷上了靖苏cp。 原因无他,心疼而已。心疼梅长苏步步为营,满心算计。心疼萧景琰被念了十年的人欺瞒利用,...
    o小红帽o阅读 818评论 0 3
  • 我想在生日那天对袁文雅说一些话,可能有点伤人, 不想继续这份友谊,都已经尽力了,累了, 好友都删了吧,没有意思,有...
    你是我的boy阅读 151评论 0 0
  • 元辰宫(心灵花园)练习,为了探访往生亲人,去地狱逛了一圈,不,是两圈,偶尔发现,我曾在这里工作过,是那个写生死簿的...
    宝光_光宝阅读 758评论 1 3