[原创]浅谈非主流多态

在一些不基于传统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);

 

以上代码输出结果:

5
1
[ 2, 3, 1, 5, 4 ]

 

我们定义了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);

 

以上代码的输出结果:

[ 1, 2, 3, 4, 5 ]
[ 5, 4, 3, 2, 1 ]
[ 2, 3, 1, 5, 4 ]
 

在代码中定义了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);

 

以上代码输出结果:

[ { offensive: 1, defensive: 1 },
{ offensive: 2, defensive: 5 },
{ offensive: 3, defensive: 3 },
{ offensive: 4, defensive: 4 },
{ offensive: 5, defensive: 2 } ]
[ { offensive: 2, defensive: 5 },
{ offensive: 4, defensive: 4 },
{ offensive: 3, defensive: 3 },
{ offensive: 5, defensive: 2 },
{ offensive: 1, defensive: 1 } ]
[ { offensive: 2, defensive: 5 },
{ offensive: 1, defensive: 1 },
{ offensive: 3, defensive: 3 },
{ offensive: 5, defensive: 2 },
{ offensive: 4, defensive: 4 } ]

 

sortBy这个函数可以跟据传入的两个参数:用于排序的属性,以及升序1还是降序-1,来动态生成一个提供给sort函数的用的比较两个对象的函数。

 

这个示例中同时用了函式编程和元编程的思想:把函数作为数据,在运行期动态生成和传递。这样利用了动态脚本的灵活性,达到了实现多态的目的,同时也不需要使用传统OOP中过于精致和复杂的类和接口的设计。

 

欢迎各位指点和交流 @liuming

你可能感兴趣的:(JavaScript,多态,元编程,函式编程)