国庆前,参与了一个c# .net 项目,真正从新体验了一把搬砖感受:在一个多月时间好像不加任何思考,不断敲键盘加代码。我想,这也许是行业内大部分中小型公司程序猿的真实写照:都是坐在电脑前的搬砖工人。不过也不是没有任何收获,在搬砖的过程当中我彷佛发现了一些现象和形成这些现象背后的缘由及OOP思惟、习惯模式。和大部分IT公司同样,这间公司在行业里存在了必定时间(不是初创)因此在产品和技术方面有必定的积累,通俗点就是一堆现成的c# .net 代码。而后就是项目截止日期压力。为了按时完成任务的我只能在原有代码基础上不断加功能,根本没有机会去考虑用什么样的代码模式、结构去达到更好的效果。在这个过程当中有个有趣的现象引发了个人注意:基本上我只需按照某种流程(多数是业务需求)一个个增长环节就能够实现一项完整功能,固然我是不会计较这些环节对软件其它部分是否产生影响,又或者之后代码维护会不会很麻烦,只要能及时交货就行。想一想这种作法偏偏是面向对象编程或所谓行令式编程的特色,即:经过逐行执行命令引导程序的状态改变,最终状态就是运行程序的结果了,或者就是功能的实现了。经过一行行增长代码最终总会到达预期的状态,不是吗。这正是OO编程的思惟模式:由于程序状态体如今每行代码上,随时能够检查,验证思路,因此OOP比较容易上手(相对函数式编程而言)。前端
回顾一下函数式编程:好像很难按照天然逻辑思惟顺序来实现一个功能,这是由于函数式编程是一种嵌套式间接性的编程模式,即程序是在某种嵌套里运行的。函数式编程又被称为monatic-programming,即在monad里编程。monad就是我所说的嵌套,是一种类型结构,最经常使用的是Future类型。在现代编程里多线程编程很是广泛,实际上每每咱们离不开各类各样的Future。举个形象的例子:若是实现把脏水从A点引到B点输出纯净水做为某种函数式程序,编程如同搭建管道网。必须首先准备好各式各样功能的喉管,实现每种喉管的特殊功能如过滤、消毒等,而后再链接组合造成送水管道。mongodb
我在进行函数式编程时老是要把因此问题前先后后都考虑清楚了才能开始动手。首先会把一项功能的全部环节先总结出来,这些都是一些函数。而后尝试把这些函数的类型统一了,就像上面提到的喉管同样,由于不一样规格的喉管是没法链接的。一样,不一样类型的嵌套monad是没法实现函数组合的。而后先根据需求实现这些函数的输入输出,最后把这些函数组合起来造成完整功能。你看,在函数式编程里是没法作到随意想到那就写到那的,必须先进行总体的思量。因此,函数式编程在代码重用和维护上有先天的优点。这个例子也体现了函数式编程的思惟模式。数据库
下面我想用一个实际的例子来示范函数式编程模式:前面几篇讨论的例子里有一个是把前端httpclient上传httpserver的图片存放入服务器端mongodb数据库的。如今发现客户端上传图片数据流有困难,但愿上传一个图片下载网址,由httpserver自行下载图片并写入mongodb。单从这个功能来说,应该由几个环节组成:编程
一、从上传的数据中抽出图片下载网址json
二、下载图片,经过http的request请求,从response里获取图片数据流c#
三、经过mongodb的count功能获取图片系列序号数组
四、将图片写入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!") } } }~
是否是很容易读懂理解?实际上咱们把复杂的细节函数藏在背后。而这些函数是高度可重复利用的,这也是咱们在动手以前通盘考虑的成果。