在一些不基于传统OOP模型的编程语言中,由于没有或者不强调类与对象的关系,实现多态的方法并不能够通过类间的继承或者接口来实现。这种情况下也许函式编程和元编程的思想能给我们一点启发,本文用了没有类也没有接口的JavaScript作为用例来表述。示例代码中只包含了最基本的逻辑,不包含太多的容错处理,代码在Node.js下运行通过。
我们可以先从一个最简单的问题开始着手:查找一堆数字中的最大值和最小值。这里假设把一堆整数放到数组中并用擂台算法的实现,其实查找最大值和最小值两个动作的其它部份逻辑都是相同的,唯一不同的就是比较两个值大小的逻辑。在找最大值的时候,我们要判断新来的值是否比较大;在找最小值时则相反。
先看代码:
var numbers = [2,3,1,5,4]; var maximum = function(max, current) { return max > current ? max : current; }; var minimum = function(min, current) { return min < current ? min : current; }; console.log(numbers.reduce(maximum)); console.log(numbers.reduce(minimum)); console.log(numbers);
以上代码输出结果:
我们定义了maxinum和mininum两个函数,它们都接受两个值:目前为止的最大值,和一个还没进行比较的新值。然后执行对比的逻辑,并返回更大或者更小的一个。
我们用到了ES5标准中的Array.prototype.reduce函数,对整数组进行递减运算,并把用于计算最大值或最小值的函数作为参数传到reduce中(类似于事件编程中的回调函数)。在reduce开始执行时,它会把上一次reduce的值传进去作为函数的第一个参数,再把当前正在处理的值传为第二个参数。由于每一次reduce执行时,都是设用maximum或者mininum来得到一个结果,所以实际上,我们的操作是把每一轮选出来比较大的数,和数组中下一个数进行比较,以此类推。
关于reduce的详情,可参见:https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/Reduce
又如排序问题也基本相同,升序和降序的逻辑也绝大部份相同,只是比较两个数大小的那部份不一样。
先看代码:
var numbers = [2,3,1,5,4]; var ascendant = function(left, right) { if (left == right) return 0; return left > right ? 1 : -1; }; var descendant = function(left, right) { if (left == right) return 0; return left < right ? 1 : -1; }; console.log(numbers.concat().sort(ascendant)); console.log(numbers.concat().sort(descendant)); console.log(numbers);
以上代码的输出结果:
在代码中定义了ascendant和descendant两个函数,它们都可接受两个参数,并跟据传入的两个数的大小情况,返回相应的0、-1或者1。这两个函数目前只处理数字类型,我们会在下一节描述如何处理复合数据。
在ES3中已经定义了一个Array.prototype.sort函数用于排序数组,该函数接受另一个对比函数作为它的第一个参数,并且会把邻近的两个值传到这个对比函数中,由这个对比函数执行比较两个数大小的逻辑,最后跟据返回值是否等于0、大于0或小于0三种情况来决定如何排序数组。
在调用sort之前还执行了一次concat,这是一个用于复制数组却又想偷懒时用的小技巧。因为sort会改变调用它的数组的顺序,所以要复制一份以保证原数组不被修改。这也是函式编程的思想之一,避免外部状态和可变数组对函数返回结果所造成的影响。
关于sort的详情,可参见:https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort
在实现应用中,经常会遇到一个由复合数据类型组成的数组,比如说对象数组。这时可能需要根据每个对象中的某个值进行排序。如以下代码中有若干玩家players对象,要对攻击力offensive进行升序排列,以及对防御力defensive进行降序排列。
代码如下:
var players = [ {offensive: 2, defensive: 5}, {offensive: 1, defensive: 1}, {offensive: 3, defensive: 3}, {offensive: 5, defensive: 2}, {offensive: 4, defensive: 4}, ]; var sortBy = function(fieldName, orderDirection) { var field = fieldName; var order = orderDirection < 0 ? -1 : 1; return function(left, right) { if (left[field] == right[field]) return 0; return (left[field] > right[field] ? 1 : -1) * order; }; }; console.log(players.concat().sort(sortBy("offensive", 1))); console.log(players.concat().sort(sortBy("defensive", -1))); console.log(players);
以上代码输出结果:
sortBy这个函数可以跟据传入的两个参数:用于排序的属性,以及升序1还是降序-1,来动态生成一个提供给sort函数的用的比较两个对象的函数。
这个示例中同时用了函式编程和元编程的思想:把函数作为数据,在运行期动态生成和传递。这样利用了动态脚本的灵活性,达到了实现多态的目的,同时也不需要使用传统OOP中过于精致和复杂的类和接口的设计。
欢迎各位指点和交流 @liuming