如何优化冗长的条件语句

前言
我不讨厌简短的 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】策略模式是通过多态来实现不同子类的选取,是多态调用具体算法的展现。
总结
条件语句的优化,不是上述一种方式可以完成的,往往是上述几种方法的结合使用。
参考文章: