函数式编程领进门

业务方提出一个需求

需要一个函数,能把家庭地位数组中每个单词的首字母变为大写。
这样单词会好看一点。

const arr = ['woman', 'children', 'dog', 'man'];

const upperFirst = function (arr) {
    const newArr = [];
    for (const word of arr) {
        newArr.push(word[0].toUpperCase() + word.slice(1));
    }
    return newArr;
};

console.log(upperFirst(arr));
// ['Woman', 'Children', 'Dog', 'Man']

业务方进行一次需求改动

需要一个函数,既能把数组中每个单词的首字母变为大写,又能把数组中最后一个单词的首字母变为大写。
这样单词会更好看一点。
业务是天,业务是地,业务的需求是天地。
不要跟我说什么需求分析,不要跟我说什么代码复用。
老夫从来都是一把梭。
找到原来的upperFirst函数,copy,paste,改函数名,重新阅读下源码找到对应逻辑改。
好,perfect!

const upperFirstAndLast = function (arr) {
    const newArr = [];
    for (const word of arr) {
        const wordLength = word.length;
        newArr.push(word[0].toUpperCase() + word.slice(1,-1)+word[wordLength-1].toUpperCase());
    }
    return newArr;
};

console.log(upperFirstAndLast(arr)); 
// ['WomaN', 'ChildreN', 'DoG', 'MaN']

业务方再进行需求改动

觉得家庭地位不够明显啊,给每个单词加上序号吧。
开发心想你大爷的,这下伤筋动骨了,改动的要多。
业务方看出开发的心事,微微一笑很倾城,你大爷还是你大爷。
既然是大爷的意思,那小爷我就照着大爷的意思去做。

const upperFirstAndLastAddOrder = function (arr) {
    const newArr = [];
    for (let i = 0; i

心有猛虎,细嗅蔷薇。

细心一点的同学发现,业务方新加的两个需求实现,可以抽象出来。
把upperFirst,upperLast,addOrder组合一下就可以满足业务需求。
而且以后这几个函数,可以单独拎出来复用。。

const upperFirst = function (arr) {
    const newArr = [];
    for (const word of arr) {
        newArr.push(word[0].toUpperCase() + word.slice(1));
    }
    return newArr;
};

const upperLast = function (arr){
    const newArr = [];
    for (const word of arr) {
        const wordLength = word.length;
        newArr.push(word.slice(0,-1)+word[wordLength-1].toUpperCase());
    }
    return newArr;
};

const addOrder = function(arr){
    const newArr = [];
    for (let i = 0; i

这个满足需求的过程二中,我们没有刻意去用函数式编程的方式,但我们很自然地体现了函数式编程的几个原则。
这就是所谓的无形装逼,最为致命。
这几个原则是纯函数通过组合的手段来复用已有功能去解决问题
纯函数体现了面向对象的单一责任原则开放封闭原则
单一责任原则是指一个函数干一个事,并且安全无副作用,他好我也好。
开放封闭原则是指对内修改封闭,对外新增开放。
在过程二中对于需求的变化,我们没有修改已有的纯函数,我们新增纯函数。
通过组合的手段来复用已有功能去解决问题则是体现接口分离原则
在这里我们把函数看作接口。
因为我们用多个专门的接口组合来解决问题,而不是像过程一直接提供大接口解决问题。

SOLID意思为可靠的,要做到可靠的编码原则。
现在还剩里氏替换原则依赖倒置原则,用什么来套。

柯里化

const _ = require('ramda');
const say = (prefix, name, greeting) => `${greeting}, ${prefix} ${name}!`;
const curreiedSay = _.curry(say);

curreiedSay('Mr','Sam','Hello'); // "Hello, Mr Sam!"
curreiedSay('Mr','Wu','Hello'); // "Hello, Mr Wu!"
const sayHelloMr = curreiedSay('Mr', _ ,'Hello');
sayHelloMr('Sam'); // "Hello, Mr Sam!"
sayHelloMr('Wu'); // "Hello, Mr Wu!"

里氏替换原则是指父类出现的地方,实现父类接口的子类可以替换。
sayHelloMr实现了curreiedSay的prefix跟greeting接口,并持久了Mr和Hello。
对于不同男士的问候需求,可以直接sayHelloMr('name'),
而不用重复curreiedSay('Mr','name','Hello')里的Mr和Hello参数。

依赖倒置原则

业务方需求两个功能,一个是去重后进行数字累加,一个是去重后进行数字乘积。
我们对需求进行分析,发现需要数组去重功能,数组元素累加功能,数组元素乘积功能。
这三个功能的关系,用专业术语表达正交性,说人话就是没有相互依赖。
但是高层功能的实现,需要底层功能组合的顺序依赖。
这个底层功能组合的顺序依赖,就是你对业务需求的抽象。
这体现了依赖倒置原则,即高层和底层模块都要依赖于抽象。

const _ = require('ramda');
const unique = arr => _.uniq(arr); // 数组去重
const sum = arr => _.reduce(arr, (total, n) => total + n); // 数组元素的累加之和
const multiply = arr => _.reduce(arr, (total, n) => total * n); // 数组元素的乘积

// 从右至左, 先去重, 再执行 fn
const getTotal = function(fn){
    return _.compose(fn, unique);
}; 

const arr1 = [1, 2, 3, 4, 4, 5, 5];
const arr2 = [1, 2, 2, 3, 4, 4, 5];

const getSumTotal = getTotal(sum); // 通过柯里化产生一个新的函数
const getMultiplyTotal = getTotal(multiply);   // 通过柯里化产生一个新的函数

getSumTotal(arr1); // 15
getMultiplyTotal(arr2); // 120

下期预告

函数式编程修行靠自己(实战写法)。
过程二这个组合方式,不就是嵌套地狱嘛。
如果需求增多,嵌套更深,啊我的钛合金狗眼,代码可读性急剧下降。
业内常见的做法是链式处理。
链式的异常处理。
异步操作的组合。
并行操作的组合。

下下期预告

介绍具有社会主义思想的flux框架Ramdux,是如何组合store进行函数式的react编程。

你可能感兴趣的:(函数式编程领进门)