国庆前,参与了一个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. C#开发BIMFACE系列13 服务端API之获取转换状态

    系列目录     [已更新最新开发文章,点击查看详细] 在<C#开发BIMFACE系列12 服务端API之文件转换>中详细介绍了7种文件转换的方法.发起源文件/模型转换后,转换过程可能成功 ...

  2. Netty源码分析 (二)----- ServerBootstrap

    BootStrap在netty的应用程序中负责引导服务器和客户端.netty包含了两种不同类型的引导: 1. 使用服务器的ServerBootStrap,用于接受客户端的连接以及为已接受的连接创建子通 ...

  3. 《快照读、当前读和MVCC》

    1.快照读 快照读是基于 MVCC 和 undo log 来实现的,适用于简单 select 语句,避免了幻读. 读已提交:一个事务内操作一条数据,可以查询到另一个已提交事务操作同一条数据的最新值.( ...

  4. 2019年牛客多校第一场 B题 Integration 数学

    题目链接 传送门 思路 首先我们对\(\int_{0}^{\infty}\frac{1}{\prod\limits_{i=1}^{n}(a_i^2+x^2)}dx\)进行裂项相消: \[ \begin ...

  5. 51 nod 石子归并 + v2 + v3(区间dp,区间dp+平行四边形优化,GarsiaWachs算法)

    题意:就是求石子归并. 题解:当范围在100左右是可以之间简单的区间dp,如果范围在1000左右就要考虑用平行四边形优化. 就是多加一个p[i][j]表示在i到j内的取最优解的位置k,注意能使用平行四 ...

  6. JAVA - 一个for循环实现99乘法表

    public class Test03 {public static void main(String[] args) { int lie = 1; for (int hang = 1; hang&l ...

  7. Linux音频编程(二)声卡介绍

    一.声卡 1.声卡是audio interface,它含有hardware buffer,而这个hardware buffer是在声卡里面,不是内存.声卡的缓存是环状的,则ALSA中是将数据分成连续的 ...

  8. Java 网络编程:必知必会的 URL 和 URLConnection

    java.net.URL 类将 URL 地址进行了封装,并提供了解析 URL 地址的基本方法,比如获取 URL 的主机名和端口号.java.net.URLConnection 则代表了应用程序和 UR ...

  9. 【Redis】安装、开启以及关闭

    一.Linux环境的操作 1.1 下载安装 1.2 启动 1.3 连接Redis客户端 1.4 关闭 二.Windows和Mac下的操作 2.1 下载安装 2.2 启动 2.3 连接客户端 2.4 关 ...

  10. Java 编程语言中很少被人了解的特性-statement label

    下面的语句会编译报错或者打印什么? System.out.print("baidu site :"); https://www.baidu.com; System.out.prin ...