如何优化冗长的条件语句

前言

我不讨厌简短的 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】策略模式是通过多态来实现不同子类的选取,是多态调用具体算法的展现。

总结

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

参考文章:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏about云

hadoop开发必读:认识Context类的作用

问题导读: 1.Context能干什么? 2.你对Context类了解多少? 3.Context在mapreduce中的作用是什么? 本文实在能够阅读源码...

3424
来自专栏Crossin的编程教室

【Python 第19课】 函数

数学上的函数,是指给定一个输入,就会有唯一输出的一种对应关系。编程语言里的函数跟这个意思差不多,但也有不同。函数就是一块语句,这块语句有个名字,你可以在需要时反...

3057
来自专栏大闲人柴毛毛

三分钟理解“策略模式”——设计模式轻松掌握

实际问题: 由于超市隔三差五就要办促销活动,但每次促销活动的方式不一样,所以需要开发一个软件,营业员只要输入原价再选择活动类型后,就能计算出折扣以后的价钱。...

35614
来自专栏磐创AI技术团队的专栏

干货 | 如何写一个更好的Python函数?

《Writing Idiomatic Python》一书的作者在Medium上发表了一篇文章,给出了6个建议。

781
来自专栏用户2442861的专栏

C++ 智能指针详解

http://blog.csdn.net/xt_xiaotian/article/details/5714477

5731
来自专栏Crossin的编程教室

这些年,你们一起踩过的坑(2)

上次我们踩坑总结文章 这些年,你们一起踩过的坑(1) 受到了不少同学的认可。我也确信文中所涉及的问题是非常具有普遍性的,对绝大多数初学者都会有帮助。

1163
来自专栏take time, save time

你所能用到的BMP格式介绍(二)

一、可能你忽视的基础         在正式开始之前,我不得不从最基本的地方开始,因为这些地方大多数人会忽视的一干二净,如果不在开始进行说明,那么在后面一定会有...

2967
来自专栏行者悟空

我眼中的并发编程——Fork/Join模型

1945
来自专栏Spark学习技巧

Flink DataStream编程指南

Flink程序是执行分布式集合转换(例如,filtering, mapping, updating state, joining, grouping, defi...

1.9K7
来自专栏灯塔大数据

技术 | Python从零开始系列连载(十九)

但它的特点就是下次使用next(a)时,接着上次的断点继续运行,直到下一个yield

1123

扫码关注云+社区

领取腾讯云代金券