当前位置:编程学习 > C#/ASP.NET >>

C#函数式程序设计初探——重构应用篇

篇首语
  在基础理论篇当中已经向大家介绍了Func类、函数闭包及函数柯里化等内容,进而介绍了函数式编程在Linq当中的运用。本文将延续这一话题,继续讨论函数式在重构等方面的一些技巧,希望能对大家的工作带来一些启发。

  本文面向有一定基础的读者,如果在阅读过程中您看不懂某些术语或代码,请移步《C#函数式程序设计初探——理论基础篇》。注意,本文提供的一些思路仅供参考,切勿盲目模仿,否则后果自负。

 

主要内容

  利用闭包缓存数据,令方法按需执行,提炼重复参数

 

第一部分 利用闭包缓存数据


  首先来看一段简单的示例代码:


class Program
{
    static void Main(string[] args)
    {
        int num = 10;
        int result1 = Calculator.Square(10);
        int result2 = Calculator.Square(10);

        Console.WriteLine(result1);
        Console.WriteLine(result2);
        Console.ReadKey();
    }
}

public class Calculator
{
    private static Dictionary<int, int> SquareResult = new Dictionary<int, int>();
    public static int Square(int x)
    {
        if (!SquareResult.ContainsKey(x))
        {
            Console.WriteLine("缓存计算结果");
            SquareResult[x] = x * x;
        }
        return SquareResult[x];
    }
}Square是一个带有缓存功能的求平方算法,它将计算的结果缓存在了一个词典当中防止重复计算,这个技巧在进行很复杂的计算(比如求正弦)当中是比较有用的(空间换时间)。

  在我们的日常工作当中相信大家都写过或者遇见过类似的代码:一个词典被放置在工厂类当中作为单例容器,也就是所谓的“池模式”。

  这里的求平方只是一个为了说明问题的简单例子,现实需求往往更加复杂,而使用设计模式是需要结合实际需求场景的。设想这样的情景:如果这个计算并不像计算平方这样长期通用,而是希望“缓存词典”中的内容仅仅是在这个方法(算法)的内部多次使用(即离开了Square的调用函数Main,我就想要释放这个词典),那么我就毫无必要为了解决一个算法的时间性能优化的具体问题点,而引入一个新的静态类来污染整个面向对象结构,一方面这样做导致了类数量的膨胀,另一方面调用函数Main与静态类Calculator发生了强耦合调用关系。如果我们的系统中到处都充满了Calculator这样的类,就大大增加了理解、维护和接手的成本。

  在这种情况下,很容易我们就能想到把这个函数定义到调用函数的内部,这个思路和《重构》当中的“提炼方法”是完全相反的(恐怕是因为在Java里没法这么搞),代码如下:


class Program
{
    static void Main(string[] args)
    {
        Dictionary<int, int> SquareResult = new Dictionary<int, int>();

        Func<int,int> Square = x => {
            if (!SquareResult.ContainsKey(x))
            {
                Console.WriteLine("缓存计算结果");
                SquareResult[x] = x * x;
            }
            return SquareResult[x];
        };

        int num = 10;
        int result1 = Square(10);
        int result2 = Square(10);

        Console.WriteLine(result1);
        Console.WriteLine(result2);
        Console.ReadKey();
    }
}这样一来,我们就从系统当中“干掉”了一个扎眼的静态类,再者,我们发现在后续的调用代码中并没有使用SquareResult这个集合变量,那么我们可以说这个变量同样污染了函数空间,于是乎想到通过柯里化的方式把这个集合移动到Square方法的内部:


class Program
{
    static void Main(string[] args)
    {
        Func<Func<int,int>> GetSquareFunc = () => {
            Dictionary<int, int> SquareResult = new Dictionary<int, int>();
            return x => {
                if (!SquareResult.ContainsKey(x))
                {
                    Console.WriteLine("缓存计算结果");
                    SquareResult[x] = x * x;
                }
                return SquareResult[x];
            };
        };

        Func<int,int> Square = GetSquareFunc();

        int num = 10;
        int result1 = Square(10);
        int result2 = Square(10);

        Console.WriteLine(result1);
        Console.WriteLine(result2);
        Console.ReadKey();
    }
}首先我们定义了一个返回函数的函数起名叫GetSquareFunc,在其中定义了一个词典的局部变量,并在这个函数内部返回一个闭包,这个闭包的内部调用了我们的词典进行缓存和判断。在调用时,我们首先要通过GetSquareFunc来动态生成一个求平方函数,之后使用这个运行时产生的函数来进行求平方操作。

  在这里我们看到了如何利用闭包与柯里化的方式缓存数据,使用了函数式的手段进行代码重构之后我们的世界清静多了,不过有人可能会说这么做有点“反OO”,这不是把算法和调用耦合到一个调用方法里了吗?是的,重构总会有一些副作用,所以说任何重构与模式的使用都是要结合需求情境的。同时也有人会问,你这不是多此一举吗,易做图嘛不直接把这个缓存逻辑内联在算法里呢?那么我想问,难道你希望用一堆#region/#endregion让代码成为很长的一坨吗?

   嗯,关于重构的话题已经脱离了本文的范围,而且牵扯到心理学、易做图症、洁癖症等……总之,这是函数式的一个应用,我们还是从需求出发!

 

第二部分 令方法按需执行

  首先来看一段代码:


static void Main(string[] args)
{bool result = DoSth(2, GetList());
    Console.WriteLine("执行结果" + result);
           
    Console.ReadKey();
}

static bool DoSth(int x, List<object> list)
{
    if (x < 10) return false;补充:软件开发 , C# ,

CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,