C#/.Net · 2018年5月17日 0

大量连续的if-else怎么办

简介

在大部分语言的实际开发中,使用if-else做逻辑验证判断是必要的,但是随着业务的复杂程度上升,if的数量也是急剧上升;有时候增加一个业务条件,会导致产生一个完全新的代码路径,可能if-else的数量就会翻倍;然而这个时候我们的代码看起来逻辑就会很不清晰,而且可能层层嵌套,阅读困难。
这里就介绍如何让大量的连续的if看起来更舒服,更具有可读性,以及在极端条件下的解决方案(如数百数千的逻辑判断)。

1. if-else到if-return

首先我们肯定要避免if多层嵌套,这种写法非常不利于阅读:

if (a)
{
    f1();
    if (b)
    {
        if (c)
        {
            f4();
        }
        f2();
    }
    else
    {
        f3();
    }
}

深层的if更难明确的说出对应的完整的执行条件,重构减少if的嵌套,是提升代码质量的首要任务。


如果只是少量的且逻辑极少变化的if-else可以采用if-return方式:

if (a1)
{
    f1();
}
else if (a2)
{
    f2();
}
.....
if (a1)
{
    f1();
    return;
}
if (a2)
{
    f2();
    return;
}

....

在此建议在开发中减少else的使用;因为在代码执行中输入的不确定性,导致进入else代码片段很可能不是我们想要的结果,举个例子:

private static void showNum(int innum)
{
    if (innum == 1)
    {
        Console.WriteLine("this one");
    }
    else if (innum == 2)
    {
        Console.WriteLine("this two");
    }
    else
    {
        Console.WriteLine("this three");
    }
}

很多时候我们写的方法是为了别人调用,谁都不能保证这个方法接收到的参数一定是我们所预期的;if有明确的条件判断,从代码的角度讲一定是所预期条件的(但是if也会出现人为的疏漏),相对的else是对if条件的否定,对于一个条件取否,那符合else条件的太多太多了,而且在实际中人很难把else内的所有情况一一列出,难免疏漏。
实际中决不能认为【真】取否【非真】就一定等于【假】
所以使用if-return,将符合预期条件的执行并返回,所有未知情况即不在预期内的,统一error处理。

2. switch

switch虽然也可以解决大量if-else的问题,但是量大了以后依然会写成一大片的case。同样会显得代码冗长,但是确实比if-else清晰;多变量复合条件的判断的改造必须要经过逻辑上的优化才可能使用switch。
可以说switch是一个简单境况下的解决方案。

switch (innum)
{
    case 1:
        f1();
        break;
    case 2:
        f2();
        break;
    default:
        f3();
}

3. 策略模式

策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。比如每个人都要“交个人所得税”,但是“在美国交个人所得税”和“在中国交个人所得税”就有不同的算税方法。
策略模式:
定义了一族算法(业务规则);
封装了每个算法;
这族的算法可互换代替(interchangeable)。

维基百科-策略模式

这里直接使用一个简单的例子解释如何使用策略模式解决if-else。
以下是一个传统的if-else写法,求一个手机的价格:

enum IPhoneEnum
{
    IPhone6,
    IPhone7,
    IPhone8,
    IPhoneX
}
....
private static int getIPhonePrice(IPhoneEnum iph)
{
    if (iph == IPhoneEnum.IPhone6)
    {
        return 3000;
    }
    else if (iph == IPhoneEnum.IPhone7)
    {
        return 4500;
    }
    else if (iph == IPhoneEnum.IPhone8)
    {
        return 5000;
    }
    else if (iph == IPhoneEnum.IPhoneX)
    {
        return 9000;
    }
    else
    {
        return 0;
    }
}

很平常的一个方法,通过对一个个if匹配获取对应手机的价格。
接下来看一下策略模式中的写法:

class IfElse_Test
{
    static void Main(string[] args)
    {
        Context c = Context.GetInstance();
        Console.WriteLine(c.getIPhonePrice(IPhoneEnum.IPhone6).getIPhonePrice());
        Console.WriteLine(c.getIPhonePrice(IPhoneEnum.IPhone7).getIPhonePrice());
        Console.WriteLine(c.getIPhonePrice(IPhoneEnum.IPhone8).getIPhonePrice());
        Console.WriteLine(c.getIPhonePrice(IPhoneEnum.IPhoneX).getIPhonePrice());

        Console.ReadKey();
    }
}
class Context
{
    private static Context ct = null;
    private static readonly object locker = new object();
    private static Dictionary<IPhoneEnum, IIphone> phList = new Dictionary<IPhoneEnum, IIphone>();
    private Context()
    {
    }
    public static Context GetInstance()
    {
        if (ct == null)
        {
            ct = new Context();
            phList.Add(IPhoneEnum.IPhone6, new Iphone6());
            phList.Add(IPhoneEnum.IPhone7, new Iphone7());
            phList.Add(IPhoneEnum.IPhone8, new Iphone8());
            phList.Add(IPhoneEnum.IPhoneX, new IphoneX());
        }
        return ct;
    }

    public IIphone getIPhonePrice(IPhoneEnum iph)
    {
        return phList[iph];
    }
}

interface IIphone
{
    int getIPhonePrice();
}

class Iphone6 : IIphone
{
    public int getIPhonePrice()
    {
        return 3000;
    }
}
class Iphone7 : IIphone
{
    public int getIPhonePrice()
    {
        return 4500;
    }
}
class Iphone8 : IIphone
{
    public int getIPhonePrice()
    {
        return 5000;
    }
}
class IphoneX : IIphone
{
    public int getIPhonePrice()
    {
        return 9000;
    }
}
enum IPhoneEnum
{
    IPhone6,
    IPhone7,
    IPhone8,
    IPhoneX
}

策略模式中没有连续的if判断,在面向对象的基础下代码看起来更清晰,后期拓展更便捷;优点虽有但是却增加了不少的代码量,不适用于小范围的if-else的重构。还得看具体业务具体分析。

4. 责任链设计模式

在开发中有时候会遇到对于同一个输入要判断这个输入的范围,再由不同的对象分别或协同处理后才能返回结果。例如请假的流程:1-2天找技术经理可决定;3-5天找技术总监;6以上找总经理。一共涉及了3个责任人的作业,但是却不一定都触发,有一定顺序的责任制度。

对此产生了责任链设计模式,将同一种责任放在一条链上,逐个处理,完成后交由下一个责任对象处理,直至结束。

先看常规的if-else实现方式:

private static bool applyLeave(int days)
{
    if (0 < days && days < 3)
    {
        return true;
    }
    else if (3 <= days && days < 6)
    {
        return true;
    }
    else if (6 <= days)
    {
        return true;
    }
    else
    {
        return false;
    }
}

就是对于不同的天数做不同的if判断,中间需要加入新一级的审核人也只能修改整个if判断。

再来看一看责任链设计模式的实现:

class IfElse_Test
{
    static void Main(string[] args)
    {
        ApplyLeaveRequest alreq = new ApplyLeaveRequest(10);

        Approver pm = new ProjectManager("PM-1");
        Approver dm = new DepartmentManager("DM-2");
        Approver gm = new GeneralManager("GM-3");

        pm.NextApprover = dm;
        dm.NextApprover = gm;

        pm.ProcessRequest(alreq);
        Console.ReadLine();
    }
}
public class ApplyLeaveRequest
{
    public int Days { get; set; }
    public ApplyLeaveRequest(int days)
    {
        this.Days = days;
    }
}
public abstract class Approver
{
    public Approver NextApprover { get; set; }
    public string Name { get; set; }
    public Approver(string name)
    {
        this.Name = name;
    }
    public abstract bool ProcessRequest(ApplyLeaveRequest request);
}
/// <summary>
/// 项目经理
/// </summary>
public class ProjectManager : Approver
{
    public ProjectManager(string name)
        : base(name)
    { }

    public override bool ProcessRequest(ApplyLeaveRequest request)
    {
        if (0 < request.Days && request.Days < 3)
        {
            Console.WriteLine("Approve leave {0} days by {1}.", request.Days, Name);
            return true;
        }
        else if (NextApprover != null)
        {
            return NextApprover.ProcessRequest(request);
        }
        return false;
    }
}
/// <summary>
/// 部门经理
/// </summary>
public class DepartmentManager : Approver
{
    public DepartmentManager(string name)
        : base(name)
    { }

    public override bool ProcessRequest(ApplyLeaveRequest request)
    {
        if (3 <= request.Days && request.Days < 6)
        {
            Console.WriteLine("Approve leave {0} days by {1}.", request.Days, Name);
            return true;
        }
        else if (NextApprover != null)
        {
            return NextApprover.ProcessRequest(request);
        }
        return false;
    }
}
/// <summary>
/// 总经理
/// </summary>
public class GeneralManager : Approver
{
    public GeneralManager(string name)
        : base(name)
    { }

    public override bool ProcessRequest(ApplyLeaveRequest request)
    {
        if (6 <= request.Days)
        {
            Console.WriteLine("Approve leave {0} days by {1}.", request.Days, Name);
            return true;
        }
        else if (NextApprover != null)
        {
            return NextApprover.ProcessRequest(request);
        }
        return false;
    }
}

在这种模式中主要是ProcessRequest()方法中判断自己能否处理,不能处理且有下一个处理人则转交由下一个人,直到事件被处理或链上的所有人都经过;每个角色的判断独立出来,互不干扰;请求者与处理者之间解耦,无需相互了解。
责任链设计模式优点很多,但是人无完人,缺点很明显,当责任链过长有相当的性能问题;也会出现没有任何人处理的情况;在书写不当时容易产生重复引用。

5. FSM有限状态机

有限状态机(英语:finite-state machine,缩写:FSM)又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。

wiki-有限状态机

FSM有限状态机在游戏行业内使用是很多的。
在游戏行业中条件判断与状态非常多,任何一次鼠标或键盘的点击事件,都会触发大量的条件判断并影响相关的状态;比如简单的点击地面人物走动,是否有障碍物,是否有陷阱,是否触发剧情等等,人物要在多种不同的状态随时切换;这显然是无法用if-else/switch来实现的,后期根本不可能维护。

后续会补充一个FSM示例。

.
.
.
.
.
.
【本文章出自NM1024.com,转载请注明作者出处。】






>>转载请注明原文链接地址:大量连续的if-else怎么办