国庆前,参与了一个c# .net 项目,真正重新体验了一把搬砖感觉:在一个多月时间好像不加任何思考,不断敲键盘加代码。我想,这也许是行业内大部分中小型公司程序猿的真实写照:都是坐在电脑前的搬砖工人。不过也不是没有任何收获,在搬砖的过程中我似乎发现了一些现象和造成这些现象背后的原因及OOP思维、习惯模式。和大部分IT公司一样,这间公司在行业里存在了一定时间(不是初创)所以在产品和技术方面有一定的积累,通俗点就是一堆现成的c# .net 代码。然后就是项目截止日期压力。为了按时完成任务的我只能在原有代码基础上不断加功能,根本没有机会去考虑用什么样的代码模式、结构去达到更好的效果。在这个过程中有个有趣的现象引起了我的注意:基本上我只需按照某种流程(多数是业务需求)一个个增加环节就可以实现一项完整功能,当然我是不会计较这些环节对软件其它部分是否产生影响,又或者以后代码维护会不会很麻烦,只要能及时交货就行。想想这种做法恰恰是面向对象编程或所谓行令式编程的特点,即:通过逐行执行命令引导程序的状态改变,最终状态就是运行程序的结果了,或者就是功能的实现了。通过一行行增加代码最终总会到达预期的状态,不是吗。这正是OO编程的思维模式:因为程序状态体现在每行代码上,随时可以检查,验证思路,所以OOP比较容易上手(相对函数式编程而言)。

回顾一下函数式编程:好像很难按照自然逻辑思维顺序来实现一个功能,这是因为函数式编程是一种嵌套式间接性的编程模式,即程序是在某种嵌套里运行的。函数式编程又被称为monatic-programming,即在monad里编程。monad就是我所说的嵌套,是一种类型结构,最常用的是Future类型。在现代编程里多线程编程非常普遍,实际上往往我们离不开各种各样的Future。举个形象的例子:如果实现把脏水从A点引到B点输出纯净水作为某种函数式程序,编程如同搭建管道网。必须首先准备好各式各样功能的喉管,实现每种喉管的特殊功能如过滤、消毒等,然后再连接组合形成送水管道。

我在进行函数式编程时总是要把所以问题前前后后都考虑清楚了才能开始动手。首先会把一项功能的所有环节先总结出来,这些都是一些函数。然后尝试把这些函数的类型统一了,就像上面提到的喉管一样,因为不同规格的喉管是无法连接的。同样,不同类型的嵌套monad是无法实现函数组合的。然后先根据需求实现这些函数的输入输出,最后把这些函数组合起来形成完整功能。你看,在函数式编程里是无法做到随意想到那就写到那的,必须先进行整体的思量。所以,函数式编程在代码重用和维护上有先天的优势。这个例子也体现了函数式编程的思维模式。

下面我想用一个实际的例子来示范函数式编程模式:前面几篇讨论的例子里有一个是把前端httpclient上传httpserver的图片存放入服务器端mongodb数据库的。现在发现客户端上传图片数据流有困难,希望上传一个图片下载网址,由httpserver自行下载图片并写入mongodb。单从这个功能来讲,应该由几个环节组成:

1、从上传的数据中抽出图片下载网址

2、下载图片,通过http的request请求,从response里获取图片数据流

3、通过mongodb的count功能获取图片系列序号

4、将图片写入mongodb

首先,我需要把这几个环节形成函数,然后统一函数类型。无可争议,最好选择Future[A]这样的函数返回类型:

假设数据是用json格式传上来的,那得有个类型作为数据结构:

  case class UpData (pid: String, url: String)

可以如下获取上传的数据:

 entity(as[String]) { json =>
val upData: UpData = fromJson[UpData](json)
...
}

获取图片系列序号:返回Future[Long]

repository.count(upData.pid).toFuture[Long]

下载图片:这个返回Future[ByteString]

    import akka.actor.ActorSystem
import akka.http.scaladsl.model._
import akka.http.scaladsl.Http def downloadPicture(url: String)(implicit sys: ActorSystem): Future[ByteString] = {
val dlRequest = HttpRequest(HttpMethods.GET, uri = url)
Http(sys).singleRequest(dlRequest).flatMap {
case HttpResponse(StatusCodes.OK, _, entity, _) =>
entity.dataBytes.runFold(ByteString()) { case (hd, bs) =>
hd ++ bs
}
case _ => Future.failed(new RuntimeException("failed getting picture!"))
} }

写入mongodb:这个函数也返回Future[?]

    def addPicuture(pid: String,seqno: Int, optDesc: Option[String]
,optWid:Option[Int],optHgh:Option[Int],
bytes: Array[Byte]):Future[Completed] ={
var doc = Document(
"pid" -> pid,
"seqno" -> seqno,
"pic" -> bytes
)
if (optDesc != None)
doc = doc + ("desc" -> optDesc.get)
if (optWid != None)
doc = doc + ("width" -> optWid.get)
if (optHgh != None)
doc = doc + ("height" -> optHgh.get)
repository.insert(doc).toFuture[Completed]
}

好了,现在这几个函数都是Future类型的,可以进行组合了:

            val futSeqno: Future[Long] = for {
cnt <- repository.count(upData.pid).toFuture[Long]
barr <- downloadPicture(upData.url)
_ <- addPicuture(upData.pid, cnt.toInt, None, None, None, barr.toArray)
} yield cnt

futSeqNo是个组合的运算流程。注意它的类型还是future:意味这我们无法预测这个运算什么时候会完成,特别如果下载一张超大图片又或者网速缓慢的话,很可能在下载完成之前就执行了complete()。所以我们必须保证图片下载完成后才向终端httpclient返回response,就用onComplete来实现:

            onComplete(futSeqno) {
case Success(lv) => complete(lv.toString())
case _ => complete("error saving picture!")
}

所以整段宏观代码如下:

        post {
entity(as[String]) { json =>
val upData: UpData = fromJson[UpData](json)
val futSeqno: Future[Long] = for {
cnt <- repository.count(upData.pid).toFuture[Long]
barr <- downloadPicture(upData.url)
_ <- addPicuture(upData.pid, cnt.toInt, None, None, None, barr.toArray)
} yield cnt
onComplete(futSeqno) {
case Success(lv) => complete(lv.toString())
case _ => complete("error saving picture!")
}
}
}~

是不是很容易读懂理解?实际上我们把复杂的细节函数藏在背后。而这些函数是高度可重复利用的,这也是我们在动手之前通盘考虑的成果。

restapi(7)- 谈谈函数式编程的思维模式和习惯的更多相关文章

  1. 谈谈函数式编程curry

    Curry概念 The concept is simple: You can call a function with fewer arguments than it expects. It retu ...

  2. 用函数式编程思维解析anagrams函数

    //函数式编程思维分析 这个排列函数 const anagrams = str => { if (str.length <= 2) return str.length === 2 ? [s ...

  3. 【响应式编程的思维艺术】 (2)响应式Vs面向对象

    目录 一. 划重点 二. 面向对象编程实例 2.1 动画的基本编程范式 2.2 参考代码 2.3 小结 三. 响应式编程实现 四. 差异对比 4.1 编程理念差异 4.2 编程体验差异 4.3 数学思 ...

  4. 用函数式编程,从0开发3D引擎和编辑器(二):函数式编程准备

    大家好,本文介绍了本系列涉及到的函数式编程的主要知识点,为正式开发做好了准备. 函数式编程的优点 1.粒度小 相比面向对象编程以类为单位,函数式编程以函数为单位,粒度更小. 正所谓: 我只想要一个香蕉 ...

  5. swift 之函数式编程(一)

    1. 什么是函数式编程? 函数式编程是阿隆佐思想的在现实世界中的实现, 它将电脑运算视为数学上的函数计算,并且避免使用程序状态以及异变物件. 函数式编程的最重要基础是λ演算.而且λ演算的函數可以接受函 ...

  6. [译]通往 Java 函数式编程的捷径

    原文地址:An easier path to functional programming in Java 原文作者:Venkat Subramaniam 译文出自:掘金翻译计划 以声明式的思想在你的 ...

  7. restapi(6)- do it the functional way, 重温函数式编程

    再次看了看上篇博客的源代码,发现连自己都看不懂了.想是为了赶时间交货不知不觉又回到OOP行令模式了,看看下面这段代码: (post & parameters('pid,'desc.?,'wid ...

  8. Kotlin编写Processing程序(使用函数式编程思维和面向接口方式)

    写一例Kotlin编写的Processing程序,充分调用函数式编程思维和面向接口的编程思维,供自己和读者参考学习. 初衷 想要实现一行行的文字排版功能,每一行作为一个单位,可制定显示的位置.大小.文 ...

  9. 从 Racket 入门函数式编程

    一直想学学LISP,今天总算开了个头.如今学习LISP不是为了立就可以以用于实际项目的应用,而是为了学习一下函数式的思维方式,可以更加深入的了解计算的本质,可以更好的用C++, Java, Pytho ...

随机推荐

  1. gin-jwt对API进行权限控制

    前言 之前文章简单介绍了如何运行gin+vue的前后端分离开源项目,该项目是学习了Gin实践教程后结合vue-element-admin写的,该教程讲得很详细,适合入门Gin.本篇文章将介绍gin+v ...

  2. bdtrans 一个命令行下的机器翻译工具

    现如今,机器翻译技术已经越来越成熟了,尽管从整体来看机器翻译的结果还不是特别如意,但是也足以应付一般的翻译需求了.近几年机器翻译平台层出不穷,国外比较出名的翻译平台有Google翻译.必应翻译等,国内 ...

  3. 手机端apk文件安装

    1.电脑端下载豌豆荚 2.手机连接电脑,打开手机USB调试模式(设置->开发人员选项,打开开发人员选项和USB调试) 3.在电脑中双击下载好的apk文件,即可打开豌豆角,然后便可安装到手机.

  4. Asterisk13.23.1如何增加G723编码和G729编码

    文章主要将如何配置Asterisk G729的编码和G723的编码问题 今天在配置语音电话过程中踩到一个坑,就是在对接线路过程中出现了一个报错,在传到对方线路过程中出现无法转码从而导致报错. 查看了下 ...

  5. Maven学习归纳(五)——继承与聚合实例讲解

    一.Maven的继承 1.1 什么是继承? 继承:父工程拆分出很多子工程,可以通过父工程,统一管理依赖的版本 1.2 为什么要使用继承呢? 在A.jar 依赖着——>B.jar依赖着——> ...

  6. Maven学习归纳(二)——几个常用命令解析

    Maven的常用命令 第一次执行命令的时候,因为需要下载执行命令的基础环境,所以会从远程仓库下载该环境到本地仓库中 运行mvn命令,必须在pom.xml文件所在的目录 一. JavaProject的p ...

  7. mysql 复制表结构和表数据

    CREATE TABLE a1 ( id INT NOT NULL AUTO_INCREMENT COMMENT '编号', txt VARCHAR(20) NOT NULL DEFAULT '' C ...

  8. glusterfs详解及kubernetes 搭建heketi-glusterfs

    本文包含: gluster各存储卷详解.创建及使用 gluster-kubernetes搭建glusterfs存储 前言 传统的运维中,往往需要管理员手动先在存储集群分配空间,然后才能挂载到应用中去. ...

  9. MapReduce应用案例

    1 环境说明 注意:本实验是对前述实验的延续,如果直接点开始实验进入则需要按先前学习的方法启动hadoop 部署节点操作系统为CentOS,防火墙和SElinux禁用,创建了一个shiyanlou用户 ...

  10. Elastic Stack 笔记(八)Elasticsearch5.6 Java API

    博客地址:http://www.moonxy.com 一.前言 Elasticsearch 底层依赖于 Lucene 库,而 Lucene 库完全是 Java 编写的,前面的文章都是发送的 RESTf ...