38

客户端多线程编程解决思路

 4 years ago
source link: https://www.tuicool.com/articles/YrUvMrr
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

今天这篇文章主要介绍函数式编程的思想。

  • 什么是函数式编程?

  • 函数式编程有什么用?

什么是函数式编程?

面向对象:

数据和对数据的操作( 函数 )紧紧耦合

其他对象调用这些操作只需要通过接口,如果新操作就需要新接口

核心抽象模型是数据自己

核心活动是组合新对象和拓展已经存在的对象,这是通过加入新的方法实现的

函数编程:

数据与函数是松耦合的

调用函数以及将函数组合起来表达

核心抽象模型是函数,不是数据结构

核心活动是编写新的函数

函数编程就是把函数做成一个管道。可以直接拼接管道形成新的管道(函数的组合)。简单来说,函数式编程是一种强调以函数使用为主的软件开发风格。 (不明白的话,可以最后再回来看这个对比)

下面我们通过例子来简单的演示一下函数式编程的魅力。

现在的需求就是输出在网页上输出 “Hello World”。

可能初学者会这么写。

document.querySelector('#msg').innerHTML = '<h1>Hello World</h1>'  

这个程序很简单,但是所有代码都是死的,不能重用,如果想改变消息的格式、内容等就需要重写整个表达式,所以可能有经验的前端开发者会这么写。

function printMessage(elementId, format, message) { 
    document.querySelector(elementId).innerHTML = `<${format}>${message}</${format}>` 
} 
printMessage('msg', 'h1', 'Hello World')  

这样确实有所改进,但是任然不是一段可重用的代码,如果是要将文本写入文件,不是非 HTML,或者我想重复的显示 Hello World。

那么作为一个函数式开发者会怎么写这段代码呢?

const printMessage = compose(addToDom('msg', h1, echo)) 
printMessage('Hello World')

解释一下这段代码,其中的 h1 和 echo 都是函数,addToDom 很明显也能看出它是函数,那么我们为什么要写成这样呢?看起来多了很多函数一样。

其实我们是将程序分解为一些更可重用、更可靠且更易于理解的部分,然后再将他们组合起来,形成一个更易推理的程序整体,这是我们前面谈到的基本原则。

可以看到我们是将一个任务拆分成多个最小颗粒的函数,然后通过组合的方式来完成我们的任务,这跟我们组件化的思想很类似,将整个页面拆分成若干个组件,然后拼装起来完成我们的整个页面。

我们现在再改变一下需求,现在我们需要将文本重复三遍,打印到控制台。

var printMessaage = compose(console.log, repeat(3), echo)   
printMessage(‘Hello World’)

可以看到我们更改了需求并没有去修改内部逻辑,只是重组了一下函数而已。

纯函数和不可变性

纯函数指没有副作用的函数,相同的输入有相同的输出。

不可变性指入参是不可以被任何地方修改的。

var counter = increment() { return ++counter; }

这个函数就是不纯的,它读取了外部的变量,可能会觉得这段代码没有什么问题,但是我们要知道这种依赖外部变量来进行的计算,计算结果很难预测,你也有可能在其他地方修改了 counter 的值,导致你 increment 出来的值不是你预期的。

对于纯函数有以下性质:

  • 仅取决于提供的输入,而不依赖于任何在函数求值或调用间隔时可能变化的隐藏状态和外部状态。

  • 不会造成超出作用域的变化,例如修改全局变量或引用传递的参数。 但是在我们平时的开发中,有一些副作用是难以避免的,我们可以通过将其从主逻辑中分离出来,使他们易于管理。 

函数式编程有什么用?

函数即不依赖外部的状态也不修改外部的状态,函数调用的结果不依赖调用的时间和空间状态,做到无状态(不保存状态,每次都是一样的)。相对于 OOP 而言,FP 的思想则是摒弃外部状态,只依赖函数内的变量,可以说是粒度更小的面向对象---函数级别的。这使得单元测试和调试都更容易。每一个纯函数都是线程安全,更容易被并行执行。

在 FP 风格下,我们习惯将复杂逻辑切割成一个个小模块,通过组合这些模块实现新的业务功能,当有新的需求到来时,我们尽可能地复用已有模块达到目标。函数的输出仅依赖函数参数,不受任何外部环境影响。这样的函数可测试性强,也非常容易进行组合。

在具体业务中我们通常还需要权衡组件的复用性和开发体验,如果组件被拆分的过于细,固然复用性会提升,但文件数量会增加,对应的文档和沟通成本也会增加,这也是 FP 在实践过程中经常遭人诟病的点,即复用性提升后带来的额外开发成本。

我们在实践的时候,可以先尝试着去让我们的业务逻辑实现纯函数和不可变性。这样我们的代码至少可以在多线程的情况下,可以尽可能的减少问题。

数据驱动

一旦我们做到了函数式编程,你就会发现整个程序的驱动都是数据来完成的。从起点的入参开始驱动了整个链条。这样就可以让程序自然而然的做到了数据驱动。数据是可以跨进程、跨平台、跨时间的。用数据(状态)来描述上游逻辑,这样就可以实现逻辑可记录、可回放。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK