用设计模式降低循环复杂性

用设计模式降低循环复杂性

条件语句的作用是更改控制流,任何程序都离不开条件判断。但条件判断会增加程序的复杂度,过多的条件判断会导致循环复杂度(Cyclomatic Complexity)。
这种复杂度取决于3个因素:

  • 分支变量数
  • 分支数
  • 条件嵌套深度
if
	else if
		else if
			else if
				switch
					case 1:
						...
					case 2:
						...
				end switch
				......
			end if
		end if
	end if
end if

这种形状也叫 Arrow Anti Pattern。它不仅降低程序的可读性,难以扩展和重用,还会轻易地隐藏许多bug。是我们必须避免的代码。

设计模式注重于软件的重用性和扩展性,但大多数模式都可以用来降低循环复杂度。设计模式可以让不同参与者分担这些条件语句,每个参与者解决一部分问题,这样控制流程就更简单,从而降低他们的循环复杂性。

这里介绍一个用策略(Strategy)设计模式来降低循环复杂度的例子。

假设现在我们可以用 C# 写一个简单的计算器,代码如下:

using System;

public class Calculator
{
    public double Calculate(double operand1, double operand2, char operater)
    {
        double result = 0.0;
        switch (operater)
        {
            case '+':
                result = operand1 + operand2;
                break;
            case '-':
                result = operand1 - operand2;
                break;
            case '*':
                result = operand1 * operand2;
                break;
            case '/':
                if (Math.Abs(operand2) > 0.0)
                {
                    result = operand1 / operand2;
                }
                else
                {
                    throw new ArithmeticException("分母为零。");
                }
                break;
            default:
                throw new ArgumentException("未知运算符: " + operater);
        }
        return result;
    }

    public static void Main(String[] args)
    {
        args = new[] { "2", "*", "3" };

        double operand1 = Double.Parse(args[0]);
        double operand2 = Double.Parse(args[2]);
        char operater = args[1][0];
        double result = new Calculator().Calculate(operand1, operand2, operater);
        Console.WriteLine(operand1 + args[1] + operand2 + " = " + result);
        Console.ReadKey();
    }
}

现在用策略模式来重构这段代码:

模式角色:
Strategy: IProcessor
ConcreteStrategy: Adder, Subtractor, Multiplier, Divider
Context: Calculator
Strategy design pattern

using System;
using System.Collections.Generic;

public interface IProcessor
{
    double Process(double operand1, double operand2);
}

public class Adder : IProcessor
{
    public double Process(double operand1, double operand2)
    {
        return operand1 + operand2;
    }
}

public class Subtractor : IProcessor
{
    public double Process(double operand1, double operand2)
    {
        return operand1 - operand2;
    }
}

public class Multiplier : IProcessor
{
    public double Process(double operand1, double operand2)
    {
        return operand1 * operand2;
    }
}

public class Divider : IProcessor
{
    public double Process(double operand1, double operand2)
    {
        double result;
        if (Math.Abs(operand2) > 0.0)
        {
            result = operand1 / operand2;
        }
        else
        {
            throw new ArithmeticException("分母为零。");
        }
        return result;
    }
}

public class Calculator
{
    private Dictionary<string, IProcessor> processors;
    
    public Calculator()
    {
        processors = new Dictionary<string, IProcessor>();
        processors.Add("+", new Adder());
        processors.Add("-", new Subtractor());
        processors.Add("*", new Multiplier());
        processors.Add("/", new Divider());
    }

    public double Calculate(double operand1, double operand2, string operater)
    {
        double result;
        if (processors.ContainsKey(operater))
        {
            result = processors[operater].Process(operand1, operand2);
        }
        else
        {
            throw new ArgumentException("未知运算符: " + operater);
        }
        return result; 
    }

    public static void Main(string[] args)
    {
        args = new[] { "2", "*", "3" };

        if (args.Length != 3)
        {
            Console.WriteLine("Usage: Calculator < operand1 > <operater> < operand2 > ");
        }
        else
        {
            double operand1 = Double.Parse(args[0]);
            double operand2 = Double.Parse(args[2]);
            Calculator calculator = new Calculator();
            try
            {
                double result = calculator.Calculate(operand1, operand2, args[1]);
                Console.WriteLine(operand1 + args[1] + operand2 + "=" + result);
            }
            catch (Exception exp)
            {
                Console.WriteLine(exp.ToString());
            }
        }
        Console.ReadKey();
    }
}

这里针对不同的运算符使用相应的 Processor 策略进行处理以实现计算目标,避免了循环嵌套( if / else ),也避免了最简单的开关判断(switch)。

本文讨论了使用设计模式来降低循环复杂性的方法。通过使用设计模式重构具有更高循环复杂性的代码可以带来灵活,可配置和可扩展的系统。但是,由于需要更多的类相互协同工作,他们之间存在耦合,因此这种重构增加了解决方案的结构复杂性。因此,这些例子的应用可能更适合在条件语句可以从若干复杂分支中进行选择的时候使用,而这些分支可能会随时间而发生变化。这种变化可以是这些分支的数量以及每个分支内的功能。一个典型的例子可能是数据通信应用程序中的消息接收器,它传递许多不同类型的消息。每种消息类型都可以由单独的消息处理程序处理。这些消息处理程序可以按策略或责任链进行安排。不仅每个消息处理程序的内聚性都更高,而且生成的应用程序还可以扩展以处理较新的消息类型。

 173 total views

Author: Albert

Leave a Reply