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# ,