你的位置:首页 > 软件开发 > ASP.net > 【转载】如何写一个框架:步骤(上)

【转载】如何写一个框架:步骤(上)

发布时间:2016-06-21 22:00:12
说 明:作者也没写过什么框架,只是分享一些自己的理解,抛砖引玉罢了。如果你写过一些框架可能会产生一些共鸣欢迎讨论,如果你正在写或正打算写一个框架可能 会给你一些启发。本文以为较长可能会分多个篇博客来写,现在能想到的是主要分为步骤、模式两部分。如果你觉得好,按一个推荐举手之劳让更多 ...

说 明:作者也没写过什么框架,只是分享一些自己的理解,抛砖引玉罢了。如果你写过一些框架可能会产生一些共鸣欢迎讨论,如果你正在写或正打算写一个框架可能 会给你一些启发。本文以为较长可能会分多个篇博客来写,现在能想到的是主要分为步骤、模式两部分。如果你觉得好,按一个推荐举手之劳让更多的人可以看到。

步骤

定位

所谓定位就是回答几个问题,我出于什么目的要写一个框架,我的这个框架是干什么的,有什么特性适用于什么场景,我的这个框架的用户对象是谁,他们会怎么使用,框架由谁维护将来怎么发展等等。

  1. 如果你打算写框架,那么肯定心里已经有一个初步的定位,比如它是一个缓存框架、Web MVC框架、IOC框架、ORM/数据访问框架、RPC框架或是一个用于Web开发的全栈式框架。
  2. 是否要重复造轮子?除非是练手项目,一般我们是有了解决不了问 题的时候才会考虑不使用既有的成熟的框架而重复造轮子的,这个时候需要列出新框架主要希望解决什么问题。有关是否应该重复造轮子的话题讨论了很多,我的建 议是在把问题列清后进行简单的研究看看是否可以通过扩展现有的框架来解决这个问题。一般而言大部分成熟的框架都有一定的扩展和内部组件的替换能力,可以解 决大部分技术问题,但在如下情况下我们可能不得不自己去写一个框架,比如即使通过扩展也无法满足技术需求、安全原因、需要更高的生产力、需要让框架和公司 内部的流程更好地进行适配、开源的普适框架无法满足性能需求、二次开发的成本高于重新开发的成本等等。
  3. 主打轻量级?轻量级是很多人打算自己写一个新框架的原因,但我 们要明白,大部分项目在一开始的时候其实都是轻量级的,随着框架的用户越来越多,它必定需要满足各种奇怪的需求,在经过了无数次迭代之后,框架的主线流程 就会多很多扩展点、检测点,这样框架势必变得越来越重(从框架的入口到框架的工作结束的方法调用层次越来越多,势必框架也就越来越慢),如果你打算把框架 定位于一个轻量级的框架的话,那么在今后的迭代过程中需要进行一些权衡,在心中有坚定的轻量级的理念的同时不断做性能测试来确保框架的轻量,否则随着时间 的发展框架可能会越来越重进而偏离了开始的定位。
  4. 特性?如果你打算写一个框架,并且只有轻量级这一个理由的话,你或许应该再为自己的框架想一些新特性,就像做一个产品一样,如果找不出两个以上的亮点,那么这个产品不太可能成功,比如你的新框架可以是一个零配置的框架,可以是一个前端开发也能用的后端框架。
  5. 其它?一般来说框架是给程序员使用的,我们要考虑框架使用的频度是怎么样的,这可能决定的框架的性能需求和稳定性需求。还有,需要考虑框架将来怎么发展,是希望走开源路线还是商业路线。当然,这些问题也可以留到框架有一个大致的结构后再去考虑。

我们来为本文模拟一个场景,假设我们觉得现有的Spring MVC等框架开发起来效率有点低,打算重复造轮子,对于新框架的定位是一个给Java程序员使用的轻量级的、零配置的、易用的、易扩展的Web MVC框架。

调研

虽然到这里你已经决定去写一个框架了,但是在着手写之前还是至少建议评估一下市面上的类似(成熟)框架。需要做的是通读这些框架的文档以及阅读一些源码,这么做有几个目的:

  1. 通过分析现有框架的功能,可以制定出一个新框架要实现的功能列表。
  2. 通过分析现有框架的问题,总结出新框架需要避免的东西和改善的地方。
  3. 通过阅读现有框架的源码,帮助自己理清框架的主线流程为总体设计做铺垫(后面总体设计部分会更多谈到)。
  4. 如果能充分理解现有的框架,那么你就是站在巨人的肩膀上写框架,否则很可能就是在井底造轮子。

新开发一个框架的好处是没有兼容历史版本的包袱,但是责任也同样 重大,因为如果对于一开始的定位或设计工作没有做好的话,将来如果要对格局进行改变就会有巨大的向前兼容的包袱(除非你的框架没有在任何正式项目中使 用),兼容意味着框架可能会越来越重,可能会越来越难看,阅读至少一到两个开源实现,做好充分的调研工作可以使你避免犯大错。

假设我们评估了一些主流框架后已经很明确,我们的MVC框架是一个Java平台的、基于Servlet的轻量级的Web MVC框架,主要的理念是约定优于配置,高内聚大于低耦合,提供主流Web MVC框架的大部分功能,并且易用方面有所创新,新特性体包括:

  1. 起手零配置,总体上约定由于配置,即使需要扩展配置也支持通过代码和配置文件两种方式进行配置。
  2. 除了Servlet之外不依赖其它类库,支持通过插件方式和诸如Spring等框架进行整合。
  3. 优化的项目结构,不需要按照传统的Java Web项目结构那样来分离代码和WEB-INF,视图可以和代码在一起,阅读代码更便利。
  4. **和框架本身更紧密,提供Action、Controller和Global三个级别的"**"(或者说过滤器)。
  5. 丰富的Action的返回值,返回的可以是视图、可以是重定向、可以是文件、可以是字符串、可以是Json数据,可以是Javascript代码等等。
  6. 支持针对测试环境自动生成测试的视图模型数据,以便前端和后端可以同时开发项目。
  7. 支持在开发的时候自动生成路由信息、模型绑定、异常处理等配置的信息页面和调试页面,方便开发和调试。
  8. 提供一套通用的控件模版,使得,并且支持多种模版引擎,比如Jsp、Velocity、Freemarker、Mustache等等。

嗯,看上去挺诱人的,这是一个不错的开端,如果你要写的框架自己都不觉得想用的话,那么别人就更不会有兴趣来尝试使用你的框架了。

解决难点

之所以把解决难点放在开搞之前是因为,如果实现这个框架的某些特 性,甚至说实现这个框架的主流程有一些核心问题难以解决,那么就要考虑对框架的特性进行调整,甚至取消框架的开发计划了。有的时候我们在用A平台的时候发 现一个很好用的框架,希望把这个框架移植到B平台,这个想法是好的,但之所以在这以前这么多年没有人这么干过是因为这个平台的限制压根不可能实现这样的东 西。比如我们要实现一个MVC框架,势必需要依赖平台提供的反射特性,如果你的语言平台压根就没有运行时反射这个功能,那么这就是一个非常难以解决的难 点。又比如我们在某个平台实现一个类似于.NET平台Linq2Sql的数据访问框架,但如果这个目标平台的开发语言并不像C#那样提供了类型推断、匿名 类型、Lambda表达式、扩展方法的话那么由于语法的限制你写出来的框架在使用的时候是无法像.NET平台Linq2Sql那样优雅的,这就违背了实现 框架的主要目的,实现新的框架也就变得意义不大了。

对于我们要实现的MVC框架貌似不存在什么根本性的无法解决的问 题,毕竟在Java平台已经有很多可以参考的例子了。如果框架的实现总体上没什么问题的话,就需要逐一评估框架的这些新特性是否可以解决。建议对于每一个 难点特性做一个原型项目来证明可行,以免在框架实现到一半的时候发现有无法解决的问题就比较尴尬了。

分析一下,貌似我们要实现的这8大特性只有第1点要研究一下,看看如何免配置通过让代码方式让我们的Web MVC框架可以和Servlet进行整合,如果无法实现的话,我们可能就需要把第1点特性从零配置改为一分钟快速配置了。

开搞

  1. 首先需要给自己框架取一个名字,取名要考虑到易读、易写、易记,也需要尽量避免和市面上其它产品的名字重复,还有就是最好不要起一个侮辱其它同类框架的名字以免引起公愤。
  2. 如果将来打算把项目搞大的话,可以提前注册一下项目的相关域名,毕竟现在域名也便宜,避免到时候项目名和域名差距很大,或项目的.com或.org域名对应了一个什么不太和谐的网站这就尴尬了。
  3. 然后就是找一个地方来托管自己的代码,如果一开始不希望公开代码的话,最好除了本地源代码仓库还有一个异地的仓库以免磁盘损坏导致抱憾终身,当然如果不怕出丑的话也可以在起步的时候就使用Github等网站来托管自己的代码。

总体设计

对于总体设计我的建议是一开始不一定需要写什么设计文档画什么类 图,因为可能一开始的时候无法形成这么具体的概念,我们可以直接从代码开始做第一步。框架的使用者一般而言还是开发人员,抛开框架的内在的实现不说,框架 的API设计的好坏取决于两个方面。对于普通开发人员而言就是使用层面的API是否易于使用,拿我们的MVC框架举例来说:

  1. 最基本的,搭建一个HelloWorld项目,声明一个Controller和Action,配置一个路由规则让Get方法的请求可以解析到这个Action,可以输出HelloWorld文字,怎么实现?
  2. 如果要实现从Cookie以及表单中获取相关数据绑定到Action的参数里面,怎么实现?
  3. 如果要配置一个Action在调用前需要判断权限,在调用后需要记录日志,怎么实现?

我们这里说的API,它不一定全都是方法调用的API,广义上来说我们认为框架提供的接入层的使用都可以认为是API,所以上面的一些功能都可以认为是MVC框架的API。

框架除了提供基本的功能,还要提供一定程度的扩展功能,使得一些复杂的项目能够在某些方面对框架进行增强以适应各种需求,比如:

  1. 我的Action是否可以返回图片验证码?
  2. 我的Action的参数绑定是否可以从Memcached中获取数据?
  3. 如果出现异常,能否在开发的时候显示具体的错误信息,在正式环境显示友好的错误页面并且记录错误信息到数据库

一般而言如果要实现这样的功能就需要自己实现框架公开的一些类或 接口,然后把自己的实现"注册"到框架中,让框架可以在某个时候去使用这些新的实现。这就需要框架的设计者来考虑应该以怎么样的友好形式公开出去哪些内 容,使得以后的扩展实现在自由度以及最少实现上的平衡,同时要兼顾外来的实现不破坏框架已有的结构。

要想清楚这些不是一件容易的事情,所以在框架的设计阶段完全可以 使用从上到下的方式进行设计。也就是不去考虑框架怎么实现,而是以一个使用者的身份来写一个框架的示例网站,API怎么简单怎么舒服就怎么设计,只从使用 者的角度来考虑问题。对于相关用到的类,直接写一个空的类(能用接口的尽量用接口,你的目的只是通过编译而不是能运行起来),让程序可以通过编译就可以 了。你可以从框架的普通使用开始写这样一个示例网站,然后再写各种扩展应用,在此期间你可能会用到框架内部的20个类,这些类就是框架的接入类,在你的示 例网站通过编译的那刹那,其实你已经实现了框架的接入层的设计。

这里值得一说的是API的设计蕴含了非常多的学问以及经验,要在目标平台设计一套合理易用的API首先需要对目标平台足够了解,每一个平台都有一些约定俗成的规范,如果设计的API能符合这些规范那么开发人员会更容易接受这个框架,此外还有一些建议:

  1. 之所以我们把API的设计先行,而不是让框架的设计先行是因为 这样我们更容易设计出好用的API,作为框架的实现者,我们往往会进行一些妥协,我们可能会为了在框架内部DRY而设计出一套丑陋的API让框架的使用者 去做一些重复的工作;我们也可能会因为想让框架变得更松耦合强迫框架的使用者去使用到框架的一些内部API去初始化框架的组件。如果框架不是易用的,那么 框架的内部设计的再合理又有什么意义?
  2. 尽量少暴露一些框架内部的类名吧,对于框架的使用者来说,你的 框架对他一点都不熟悉,如果要上手你的框架需要学习一到两个类尚可接受,如果要使用到十几个类会头晕脑胀的,即使你的框架有非常多的功能以及配置,可以考 虑提供一个入口类,比如创建一个ConfigCenter类作为入口,让使用者可以仅仅探索这个类便可对框架进行所有的配置。
  3. 一个好的框架是可以让使用者少犯错误的,框架的设计者务必要考 虑到,框架的使用者没有这个业务来按照框架的最佳实践来做,所以在设计API的时候,如果你希望API的使用者一定要按照某个方式来做的话,可以考虑设置 一个简便的重载来加载默认的最合理的使用方式而不是要求使用者来为你的方法初始一些什么依赖,同时也可以在API内部做一些检测,如果发现开发人员可能会 犯错进行一些提示或抛出异常。好的框架无需过多的文档,它可以在开发人员用的时候告知它哪里错了,最佳实践是什么,即便他们真的错了也能以默认的更合理的 方式来弥补这个错误。
  4. 建议所有的API都有一套统一的规范,比如入口都叫 XXXCenter或XXXManager,而不是叫XXXCenter、YYYManager和ZZZService。API往往需要进行迭代和改良 的,在首个版本中把好名字用掉也不一定是一个好办法,最好还是给自己的框架各种API的名字留一点余地,这样以后万一需要升级换代不至于太牵强。

下一步工作就是把项目中那些空的类按照功能进行划分。目的很简 单,就是让你的框架的100个类或接口能够按照功能进行拆分和归类,这样别人一打开你的框架就可以马上知道你的框架分为哪几个主要部分,而不是在100个 类中晕眩;还有因为一旦在你的框架有使用者后你再要为API相关的那些类调整包就比困难了,即使你在创建框架的时候觉得我的框架就那么十几个类无需进行过 多的分类,但是在将来框架变大又发现当初设计的不合理,无法进行结构调整就会变得很痛苦。因此这个工作还是相当重要的,对于大多数框架来说,可以有几种切 蛋糕的方式:

  1. 分层。我觉得框架和应用程序 一样,也需要进行分层。传统的应用程序我们分为表现层、逻辑层和数据访问层,类似的对于很多框架也可以进行横向的层次划分。要分层的原因是我们的框架要处 理的问题是基于多层抽象的,就像如果没有OSI七层模型,要让一个HTTP应用去直接处理网络信号是不合理的也是不利于重用的。举一个例子,如果我们要写 一个基于Socket的RPC的框架,我们需要处理方法的代理以及序列化,以及序列化数据的传输,这完全是两个层面的问题,前者偏向于应用层,后者偏向于 网络层,我们完全有理由把我们的框架分为两个层面的项目(至少是两个包),rpc.core和rpc.socket,前者不关心网络实现来处理所有RPC 的功能,后者不关心RPC来处理所有的Socket功能,在将来即使我们要淘汰我们的RPC的协议了,我们也可以重用rpc.socket项目,因为它和 RPC的实现没有任何关系,它关注的只是socket层面的东西。
  2. 横切。刚才说的分层是横向的 分割,横切是纵向的分割(横切是跨多个模块的意思,不是横向来切的意思)。其实横切关注点就是诸如日志、配置、缓存、AOP、IOC等通用的功能,对于这 部分功能,我们不应该把他们和真正的业务逻辑混淆在一起。对于应用类项目是这样,对于框架类项目也是这样,如果某一部分的代码量非常大,完全有理由为它分 出一个单独的包。对于RPC项目,我们可能就会把客户端和服务端通讯的消息放在common包内,把配置的处理单独放在config包内。
  3. 功能。也就是要实现一个框架主要解决的问题点,比如对于上面提 到的RPC框架的core部分,可以想到的是我们主要解决是客户端如何找到服务端,如何把进行方法调用以及把方法的调用信息传给目标服务端,服务端如何接 受到这样的信息根据配置在本地实例化对象调用方法后把结果返回客户端三大问题,那么我们可能会把项目分为routing、client、server等几 个包。

如果是一个RPC框架,大概是这样的结构:

【转载】如何写一个框架:步骤(上)

对于我们的Web MVC框架,举例如下:

  1. 我们可以有一个get='_blank'>mvc.core项目,细分如下的包:

    原标题:【转载】如何写一个框架:步骤(上)

    关键词:

*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: admin#shaoqun.com (#换成@)。

可能感兴趣文章

我的浏览记录