在写干货以前,我想先探(qiang)讨(diao)两个问题,模式的局限性?模式有什么用?javascript
最近看到一篇文章对我启发很大,许来西在知乎的回答《哲学和科学有什么关联?》,全篇较长,这里摘录我要引出的一点:前端
科学做为一种经验主义的认识论,有着经验主义的巨大缺陷:它永远不能产生绝对正确的真理。这是概括法的本质决定的。并且值得注意的是,概括不具备惟一性。java
举一个简单的例子,咱们假设一个世界,以下图:web
举个模式的例子,Scott Wlaschin 在《Functional Programming Design Patterns》(函数型编程模式)中对比了经常使用面向对象模式、原则,在函数型编程语言里面等价实现:算法
因此我想引入第一个观点:express
很差好写代码看哲学文章不是偶然,在文章落笔以前,我有思考过在 JavaScript 这门动态,多范式,单线程,基于事件I/O的语言环境下,甚至在当前时代,模式是否还有意义?显然我不是惟一这样想的,还有篇深度好文《20年前GoF提出的设计模式,对这个时代是否还有指导意义?》。这篇文章引经据典,摘录了GoF(又称Gang of Four,即Erich Gamma, Richard Helm, Ralph Johnson & John Vlissides)在设计模式一书中观点:编程
本书的实际价值也许还值得商榷。毕竟它并无提出任何史无前例的算法或者编程技术。它也没能给出任何严格的系统设计方法或者新的设计开发理论——它只是对现有设计成果的一种审视。你们固然能够将其视为一套不错的教程,但它显然没法为经验丰富的面向对象设计人员带来多少帮助。 换言之,模式显然毫无实际用处。c#
不只如此,文章还列举了一度模式滥用致使许多弊端,可谓警钟长鸣。后端
可是……模式这一称谓仍然不断出现,直到今天咱们亦在大量使用。为何?GoF实际早设计模式的书中作出了预言:设计模式
设计师们提供一种共通的词汇储备,帮助其沟通、编写文档并探索设计方案。设计模式容许咱们立足于高级抽象层面进行探讨,而非设计标注或者编程语言,这就大大下降了系统复杂性。设计模式提高了咱们设计及与同事进行设计探讨时的切入点层级。”(第389页 简言之,模式方便了咱们的沟通,提高了思考问题的抽象层级。
这个意义很是巨大,想象一下没有 MVC 架构模式,可能全部的 Web 框架必然的会实现一套几乎解决一样问题的方案,可是命名和文档却各不同,当你去看一个新的框架文档的api 接口,从头至尾看完之后才恍然大悟,这不就是以前用的框架里面的 XXX 相似吗,这样的编程世界简直地狱。庆幸的是,得益于计算机科学家(码农)对问题和方案持续的抽象成模式,使得当前高度复杂的计算机科学也能获得合理分层和适配,大大简化了学习和沟通的成本。
为了感谢模式,是时候学习一波了,本文要介绍的主要有三种架构模式:Middleware,MVC,DI。
相信作过 Node.js 服务端开发的同窗对这个模式必定不陌生,考虑以下 Web 应用的场景:
有些处理会根据是否成功决定是否继续后面的粗粒,有些处理会生成额外的数据,还有的要求拦截某些处理的开始和结束,最后异常处理和记录日志要求必定被执行。
通常的解决方法是用嵌套条件判断结合 try catch finally return 等控制语句,可是这样的方案会致使代码碎片化和复制粘贴的编码风格,由于控制流和逻辑耦合到了一块儿。理想的方案应当以下:
这些场景由来已久,好久之前J2EE总结了 Intercepting Filter 式,有兴趣你们能够看看这篇文(lun)章(wen),里面由浅入深提到三种方案,其中最初级的方案代码以下:
public class DebuggingFilter implements Processor {
private Processor target;
public DebuggingFilter(Processor myTarget) {
target = myTarget;
}
public void execute(ServletRequest req,
ServletResponse res) throws IOException,
ServletException {
// preprocess
target.execute(req, res);
// post-process
}
}
复制代码
这个和 express 和 Koa 的中间件模式极其类似,可是由于静态语言自己一些特征,致使最后造成的企业级代码极其繁琐,而且有许多局限性。最主要的问题是处理模块之间难以重用和共享数据,由于 ServletRequest ServletResponse 没法动态添加属性。以致于 JavaEE 把这个模式的适用性加了许多限制,包括和核心处理逻辑分开。
在动态语言的世界里面,咱们能够很方便的往 req 和 res 里面添加数据(基于约定),由于没有了不少 OOP 世界里面的”束缚“,Node.js 的实现一般更加优雅和通用。
express 实现现在普遍接受的 Middleware 中间件模式。中间件的意思是在 请求 和 响应 中间执行的函数(为了区分另外一个中间件),签名以下:
var express = require('express');
var app = express();
复制代码
然而在一些稍微复杂点的业务中,好比一个网站有管理端和用户端,两个端至关于独立的app。express 4.0 提供了一个很是强大的功能 Router。Router 拓展了链式决策变成树形决策,可让 express 更好的支持大型项目。
/*
文件 bird.js
*/
var express = require('express')
var router = express.Router()
router.get('/', function (req, res) {
res.send('Birds home page')
})
module.exports = router
/*
文件 app.js
*/
var birds = require('./birds')
// ...
app.use('/birds', birds)
复制代码
Koa 的异步中间件模式-洋葱模型,相比 Express,其中间件函数返回 Promise,支持 async/await,而且能够轻松实现前置和后置的处理。毫无疑问这个模式更加先进,一些在 express 里面很差实现的拦截处理逻辑,好比异常处理和统计时间,在 Koa 里用一个中间件就能搞定。然而遗憾的是 Koa 自己只提供了 Http 模块和洋葱模型的最小封装。
MVC 模式也须要介绍吗,咱们每天都在聊 MVC,无论前、后端框架,说一句 MVC,对一下眼神,基本肯定对方懂你了。
事实是,前端框架已经不适合用 MVC 讨论,这个模式从1979年提出以来,做为万精油模式,在各个框架和场景中被套用,背负了太多的历史包袱,你们能够看 winter 的文章 谈谈UI架构设计的演化。拨乱反正我以为有但愿,讨论前端框架你们之后统称 MV 模式就行了,就是模型和视图分离。
咱们今天要讲的 MVC 模式是指在服务器上(后端) MVC 模式,它的定义经受了时间和实践的检验,在许多企业级 Web 框架的实现中高度一致。先列举场景:
若是你的网站只有几个简单的页面,全部逻辑都写在 Controller 里面,是没有问题的。随后网站迅速的增加,你发现,
咱们作一个数学模型模拟极端状况,你们很容易能看到问题
看到上面的两种模式,是否是已经开始想,那有没有一个框架同时是 Koa + Router + MVC 呢,推荐你们一个很是好用的企业级 Web 框架 ThinkJS 3.0,最新版的 ThinkJS 集成了大量最佳实践和完善的文档,无论是学习或者企业级开发都很是推荐。并且 ThinkJS 一样实现了接下来要讲的模式。
仍是先说场景,假如服务端须要实现session,前期考虑到成本和用户量,单台服务器存到文件就够用了。后期若是用户量大的时候,须要横向扩展(Scale-out),就把 session 实现基于中心化的 Redis 服务。
咱们系统设计目标是:
解决这类系统扩展性问题有一个很是著名的设计原则 控制反转(IoC Inversion of control),而 依赖注入(DI dependency injection) 就是其中的一个实现模式。
DI 的基本思路是这样,首先咱们的代码不能依赖具体的服务,须要总结概括出一套抽象接口,业务实现依赖接口,而服务实现接口,最后经过框架专门负责建立和提供接口的实例
本文介绍的三个架构模式,你会发现几乎在全部的Web框架实现都大同小异,这就是模式的好处。模式的意义相似于 IoC,我关注抽象和接口,抹平了具体语言特性下的细节问题,帮助咱们更好的学习,沟通和思考。
-- EOF --