【原创】如何写一个框架:模式

 

模式

 

接下来去聊一聊框架设计中的一些常见设计模式,这和传统的一些设计模式不一样(以前写过 无废话C#设计模式系列文章,有兴趣的读者能够去看一下),这里聊的一些设计模式是比较高层的粗粒度的架构设计模式,主要是用于以前说的构建框架的龙骨,使得框架中的几百个类型能够有结构有条理组织在一块儿,在这些模式之中你彻底能够再去使用各类Gof设计模式,这是不冲突的。 html

 

分层

 

在以前的文章中其实已经强调过这个概念了。即便咱们写的是同一个框架,对于框架中的全部类型可能也会有多层的结构: 设计模式

  1. 纯抽象类型(接口),用于定义框架的骨骼,好比IController
  2. 半抽象类型(抽象类或带默认实现的接口),用于定义框架的骨骼而且仍是共有逻辑在载体,好比AbstractController
  3. 具体的实现类型,在骨骼的基础上的不一样的实现,好比VelocityViewEngine

在一般状况下是能够把这些类型都放到一个模块内方便阅读和管理,在一些特殊的状况下咱们能够把框架直接在层次上拆分红多层多个框架(这些父子框架之间能够继承关系),好比: 网络

  1. 若是有太多具体类型是过于具体的和业务逻辑相关的实现,并且实现的数量很是庞大
  2. 框架虽然总体解决的是一个问题,但针对不一样的环境(好比网站、桌面和移动)在实现上有比较大的差别,那么咱们能够把共同的部分放到父框架中,为不一样环境提供单独的扩展框架
  3. 若是框架会和某个业务逻辑进行衔接,若是业务逻辑的接口不稳定的话,彻底能够把这种不稳定的因素隔离出一个单独的子框架,保持父框架的问题

总之,OO的概念不必定能够应用于类型之间的关系,还能够应用到框架自己的层次关系上。 架构

 

处理器和过滤器

 

框架就像是空气净化器的主框架它会确保空气吸入并走过全部的过滤器,过滤器就像是空气净化器的多层过滤各司其职,处理器负责把新鲜的空气排出。这是一个很是很是常见的框架的设计模式,几乎全部的面向数据流的框架,好比Web框架、Web MVC框架(ASP.NET MVC、Spring MVC等)、网络框架(Netty、Mina等)都会采用这种模式。由于这种模式: app

  1. 由处理器来定义框架的主要的数据处理模型,由过滤器定义数据的加工模型,很是适合于基于数据处理的框架。
  2. 可让框架有极大的扩展性,由框架来搭建主线流程,提供一个抽象模型,由开发人员来真正实现数据的处理业务逻辑。
  3. 很是符合职责分离的原则,并且很是灵活,有的框架甚至提供了运行时根据实际状况临时构建各类过滤器和处理器的栈。

这种模式有很是多的变种,并且过滤器和处理器之间每每在概念上也会有一些融合: 框架

  1. 框架会暴露出一些事件由过滤器来处理,有的时候这些事件仅仅是发生在处理器以前(如第一个图)
  2. 有的时候在处理器执行前和执行后都会有一些事件,甚至过滤器是能够改变已处理的结果的(如第二个图)
  3. 过滤器只是订阅了全部或部分事件进行处理的一些类,只要过滤器注册到了框架那么框架就会按照必定的顺序去执行过滤器,每每过滤器是会有多个,处理器只会有一个,若是有多个的话也是最终选择一个合适的处理器来处理
  4. 有框架处理器也是职责链式的(如第三个图),容许多个处理器对数据或部分数据进行处理,这个时候处理器又有一点像过滤器了(也有一些框架没有过滤器,只有处理器链)
  5. 有一些框架的过滤器和处理器是各自独立的,它们只是管线的一部分,没法感知其它过滤器或处理器
  6. 而有一些框架的过滤器能够在过滤的过程当中直接告知框架跳事后续的过滤器,或告知框架执行哪一个处理器,甚至是告知框架跳过处理器直接提供结果
  7. 相似的,若是框架容许多个处理器,有的框架是容许处理器告知框架是否要执行后续的处理器的
  8. 大多数框架的过滤器管线在处理器以外,而有一些框架在处理器的内部安排了一组过滤器,也就是下图的整个图就是一个处理器,其中的处理器变为了执行器
  9. 有的框架的内部有多条过滤处理的管线,甚至是结合了分发器一块儿使用,咱们回想一下咱们的Web MVC框架是否是就是由分发器->处理器(处理器自己由前置过滤->处理->后置过滤+另一组前置过滤->处理->后置过滤构成)的?
  10. 有框架没有使用过滤器这个名词而是叫拦截器,本质上差别不大,若是拦截器和处理器并存那么须要辨别其差别

总之,掌握了这个重要的模式不但对本身写框架有帮助,对使用其它框架也很是有帮助。 异步

 

依赖注入

 

首先想说一个比较偏激的观点,有不少框架对于其内部的组件采用了全面的外部依赖注入,框架总体的灵活性很高耦合很低,可是这样就会致使框架自己不是那么高内聚并且会形成框架使用者一些困扰,我是不太喜欢这么作的,我框架内部的组件能够是由依赖注入的,可是最好提供一个配置中心,提供统一的注入点而不是由使用者在用的时候来初始化这个框架的结构。因此对于框架提供的依赖注入,我更以为它应该为各类由框架使用者提供的代码(好比插件、过滤器、处理器)进行依赖的解析。 分布式

对于不少框架若是它自己就是会和业务逻辑进行绑定的,那么提供一个依赖注入的入口可能必不可少(不是提供依赖解析的功能),咱们能够提供相似一个IDependencyResolver/Adapter的接口,由框架的使用者去选择合适的IOC框架或类库进行实现,使得框架建立的外部对象都可以获得依赖的解析。 ide

对于一些全栈框架,甚至能够在框架内部自带一个默认的IOC实现,使用和框架更为契合的API为使用者进行自动的依赖解析,在大多数时候咱们真的不须要一个复杂且完整的东西,咱们只须要一个简单且高效的东西。 函数

 

各类器/者(Er/Or)

 

以下列出了各类ErOr,其中粗粒度的一些类型在以前的一些模式中有提到过部分,其他部分也是不少框架的老面孔了:

 

粗粒度/高抽象层的:

  • Dispatcher
  • Consumer
  • Producer
  • Publisher
  • Subscriber
  • Handler
  • Filter
  • Interceptor
  • Provider
  • Container
  • ……

 

中粒度/中抽象层的:

  • Locator
  • Creator
  • Initializer
  • Reader
  • Writer
  • Activator
  • Finder
  • Builder
  • Selector
  • Visitor
  • Loader
  • Descriptor
  • Generator
  • Adapter
  • Listener
  • Wrapper
  • Mapper
  • Binder
  • Invoker
  • Executor
  • Detector
  • Tracer
  • Decorator
  • Mapper
  • Resolver
  • Processor
  • Advisor
  • ……

 

细粒度/底层的:

  • Locker
  • Iterator
  • Extractor
  • Accessor
  • Validator
  • Formatter
  • Converter
  • Replacer
  • Comparer
  • Manager
  • Combiner
  • Parser
  • Encoder
  • Decoder
  • Importer
  • Exporter
  • Editor
  • Modifier
  • Evaluator
  • ……

这里列的这些都是类型(类或接口)而不是方法,它们的共性就是以动词的名词形式来命名类型,通常不少时候动词对应的是方法或函数是某个操做,之因此不少框架把函数往上提成了方法的缘由是:

  1. 框架的组件通常会有不少重用而且要支持组件扩展,把函数提取为类型才能够享受到抽象、继承、多态。
  2. 不少高层次和中层次的这些ErOr每每对应的是一种设计模式,设计模式能让复杂的代码变得有调理和充实饱满。
  3. 框架的代码通常比较复杂,只有让每个组件都有很明确的职责,才能让代码清晰。固然,若是你的Replacer真的是一句replace(),你的Encoder()真是只是一句encode(),那是没有必要搞这么复杂的。若是在重构的时候咱们发现方法的逻辑有重复,而且逻辑涉及到多种实现,咱们就能够考虑把方法提高到ErOr而后经过模版方法避免重复。

在这里没法一一对每一种类型进行详细的阐述,若是你们有兴趣能够去下载一些框架的源码,进一步学习一下每种ErOr背后的模式和用法,不少名词都已经成为了标准,因此若是你也采用一样的名词来命名相关类型的话,会助于阅读你框架源码的人理解。

 

发布订阅和消息总线

 

对于分布式应用程序,咱们常常会采用消息总线或发布订阅队列来解耦各类组件。

经过发布订阅,咱们能够解耦发布者和订阅者:

  1. 让关心某个数据的组件能够订阅某个话题,且不用关心数据的来源
  2. 让产生某个数据的组件能够谈论某个话题,且不用关心是否诱人订阅了个人话题
  3. 同时能够实现不少高级功能,好比按照模式来订阅话题,消息的持久化等等

若是你的系统中有大量的异步事件,实现了发布订阅模式,那么无论未来扩展了多少事件以及事件的处理变得多少复杂,咱们都不用去改变既有的实现。

总线是一种特殊的发布订阅模式,经过总线,咱们解耦提供者和消费者:

  1. 让全部服务的提供者本身注册到总线上,告知总线提供某个服务
  2. 让全部服务的消费者在无需知道下游提供者的状况下进行服务的调用
  3. 同时总线能够作不少额外的工做,好比负载、心跳、限流、短路等

若是你的系统中提供了很是多的服务,使用了总线,服务之间的调用就不会有任何具体实现的依赖。

其实我想画一个图的,但Visio老是把箭头乱指,折腾了老半天箭头永远是斜着的,太难看了算了

你可能会问,用于分布式应用程序的模式和咱们框架的设计有什么关联?我的以为若是你从事的是一个具备状态的框架(好比IOC就是一种具备状态的框架,但MVC不该该是,或应用程序,特别是桌面和移动应用程序)的开发,那么有些时候可能会使用到一下这两种模式来进行大规模的解耦。怎么使用?用RabbitMQ、Kafka? 不是,能够本身尝试实现:

  1. 一个基于内存的容器(做为Broker),由容器来管理话题(元数据)以及上下游对象,而且进行消息的路由(转发),这就实现了发布订阅
  2. 若是要实现总线,在这个基础上扩展一下便可,能够想到若是有人订阅Request且发布Response那么它是服务的提供者(不过Request只用推送给任意一个订阅者便可),若是有人发布Request且订阅Response那么它是服务的消费者
  3. 固然,做为框架内使用的总线,咱们可能不须要去实现诸如负载、心跳、限流等功能,数据的流转也能够是同步模式的

放到最后说这个模式是由于这实际上是一个不错的练习,若是你有兴趣的话看了本文能够本身去尝试使用本文介绍的一些步骤和模式实现一个基于内存的轻量级的RabbitMQ/Kafka做为练习,谢谢阅读。另外推荐你们能够去看一下POSA面向模式的软件架构的前两卷。

相关文章
相关标签/搜索