HTTL (Hyper-Text Template Language) 是一个高性能的开源JAVA模板引擎, 适用于动态HTML页面输出, 可替代JSP页面, 指令和Velocity相似。作者是阿里巴巴工程师梁飞,本文是在拜读了HTTL的设计原则之后提炼出的部分通用设计原则。

模型划分原则

按实体域,服务域,会话域划分。

不管你做一个什么产品,都一定有一个被操作的主体,比如:服务框架管理的Service,任务框架管理的Task,Spring管理的Bean等,这就是实体域。

即然有被操作者,就一定有操作者,它管理被操作者的生命周期,发起动作,比如:服务框架的ServiceInvoker,,任务框架的TaskScheduler,Spring的BeanFactory等,这就是服务域。

服务域发起动作,在执行过程中,会有一些临时状态需要存储交换,比如:Invacation,Execution,Request等,这就是会话域。

相应的,在HTTL中:

  • Engine 为服务域

    • 它是API的入口,并负责实体域Template的生命周期管理,它是Singleton单一实例的,加载后不可变,所以是线程安全的,它的初始化过程较重,请复用单例。
  • Template 为实体域
    • 代表着被操作者,它是Prototype原型实例的,即每个模板产生一个实例,加载后不可变,同样也是线程安全的,模板变化后,将产生不同的实例,而不改变原实例。
  • Context 为会话域
    • 持有操作过程中的所有可变状态,它是ThreadLocal线程内实例的,即不和其它线程竞争使用,所以也是线程安全的,请不要跨线程传递,它的初始化过程很轻量,每次模板执行前都新建实例,执行完即销毁。

这样划分的好处是,职责清晰,可变状态集中,每个域都是无锁线程安全的,保证在大并发下,不会降低系统的活性。

这些核心领域模型也就是HTTL的API(Application Programming Interface),它是HTTL暴露给用户的最少概念,也就是上面类图中的第一列。

扩展点组装原则

按“微核+插件”体系组装。

但凡有生命力的产品,都是在扩展性方面设计的比较好的,因为没有哪个产品可以覆盖所有需求,对于开源软件尤其如此。

所以,产品只有具有良好的扩展性,允许用户或第三方参与进来,进行二次开发,才能保持生命力。

怎么样的扩展性才是最好的?通常来讲,就是没有任何功能是硬编码的,所有的功能都可被用户替换。

那要如何才能做到这样?一个重要的原则就是:平等对待第三方。

也就是凡是原作者能实现的功能,第三方也要能够在不改变源代码的前提下实现。

换言之,原作者应把自己也当作扩展者,自己添加功能时,也要用第三方扩展者同样的方式进行,而不要有特权。

要做到这一点,就需要一个良好的框架支撑,“微核+插件”是一个不错的选择,Eclipse, Maven等知名软件都采用该体系。

什么是“微核+插件”?微核,即最小化核心,内核只负责插件的组装,不带任何功能逻辑,所有功能都由可替换的插件实现,

并且,组装过程应基于统一的规则,比如基于setter注入,而不能对不同插件硬编码组装,这样可以确保没有任何功能在内核中硬编码。

比如:Spring, OSGI, JMX, ServiceLoader等都是常见的微核容器,它们负责基于统一规则的组装,但不带功能逻辑。

当然,如果你不想带这么重的框架,也可以自行实现,HTTL就采用自行实现的httl.util.BeanFactory作为组装微核。

在Engine.getEngine()中调用了BeanFatory.createBean(Engine.class, properties),

其中,properties即为httl.properties配置,BeanFatory基于setter方法,递归注入所有对象的属性。

比如:httl.properties中配置了parser=httl.spi.parsers.CommentParser,

而DefaultEngine中有setParser(Parser parser)方法,就会被注入,并且Parser本身的属性也会递归注入。

如果你需要扩展或替换HTTL的实现,请参见:扩展集成

既然非功能性的插件组装过程,可以由微核框架来完成,那功能性的组装怎么办呢?

我们应该把功能性的组装过程也封装成插件,即让大插件组装小插件,形成级联组装关系。

比如,HTTL的入口类Engine的实例也是一个插件,它负责模板的缓存,加载,解析的总调度,即你可以替换DefaultEngine实现。

只需在httl.properties中配置:engine=com.your.YourEngine,可以将现有Parser等SPI注入你的Engine。

这些插件的接口,也就是HTTL的SPI(Service Provider Interface),它是HTTL暴露给扩展者的最小粒度的替换单元,也就是上面类图中的第二列。

整体分包原则

按复用度,抽象度,稳定度分包。

  • 复用度:

    • 每种用户所需用到的类,就是同一复用粒度的,比如:使用者和扩展者,这样可以减少代码干扰,以及最大化复用。
  • 稳定度:
    • 被依赖包和依赖包的占比,如果一个包依赖很多包,那别的包变化都会引起它跟随变化,所以它就不稳定,反之即稳定, 保持被依赖者总是比依赖者的稳定度高,形成金子塔关系,这样可以防止不稳定性传染,比如a包只依赖3个包,而b包依赖10个包,那就不要让a包去依赖b包。
  • 抽象度:
    • 包中抽象类个数占比,比如包中有10个类,其中3个为抽象类(包括接口),则抽象度为3/10, 保持包的稳定度和抽象度成正比,即把抽象类(包括接口)放到稳定的包中,把具体实现类放到不稳定的包中,这样可以保持每层都有足够的扩展性。

稳定度与抽象度关系如下图:

也就是分包应该如下:

其中上面那个包不依赖其它包。所以它很稳定,应尽量把抽象类或接口放在这一层,

而下面那个包依赖了三个包,三个包变化都会引起它跟随变化,所以它是不稳定的,应尽量把具体实现类放在这一层。

因稳定度与抽象度成正比,所以不稳定度与抽象度成反比,用反比方便画图,计算方式如下:

  • (1) I = Ce / (Ca + Ce)

    • I: Instability (不稳定度)
    • Ca: Afferent Coupling (传入依赖,也就是被其它包依赖的个数)
    • Ce: Efferent Coupling (输出依赖,也就是依赖其它包的个数)
  • (2) A = Na / Nc
    • A: Abstractness (抽象度)
    • Na: Number of abstract classes (抽象类的个数)
    • Nc: Number of classes (类的个数,包括抽象类)
  • (3) D = abs(1 - I - A) * sin(45)
    • D: Distance (偏差)
    • I: Instability (不稳定度)
    • A: Abstractness (抽象度)

应该保持偏差越小越好,即下图所示交点都落在绿色反比线左右:

基于上面的原则,HTTL的包结构整体上划分为三层:(对应上面类图中的三列)

  • API (Application Programming Interface)

    • 模板引擎的使用者依赖的接口类,也是核心领域模型所在,保持最少概念,并隐藏实现细节,其中Engine类相当于微内核,只管理非功能性的扩展点的加载,不硬编码模板加载解析渲染的任何部分。
  • SPI (Service Provider Interface)
    • 模板引擎的扩展者依赖的接口类,它依赖于API的领域模型,它是模板引擎功能正交分解的抽象层,以保证用户可以最小粒度替换需要改写的地方,方便二次开发。
  • BUILT-IN (Built-in Implementation)
    • 内置扩展实现,它是SPI标准实现,也是可被用户替换的类,它包含引擎所有做的事,包括扩展点之间的组装过程(可替换DefaultEngine),以确保没有功能换不掉,即平等对待扩展者。

采用子包依赖父包风格,所以将API放在根目录,SPI接口独立子包,各种实现放在SPI的下一级子包中。

  • 使用者API导入:import httl.*;
  • 扩展者SPI导入:import httl.spi.*;

下图是HTTL所有包的不稳定度与抽象度的比值距阵:(下图为JDepend绘制)

HTTL所有核心包都是靠近反比线的,即上图中用绿色标识的点,表示分包是合理的。

注:图中黑色的点为util相关包,它们不抽象,却被很多包依赖,只是内部复用代码,不影响整体设计,用户请不要依赖HTTL的util类。

从HTTL模板引擎看软件设计原则的更多相关文章

  1. UML类图的补充及软件设计原则

    UML类图的补充及软件设计原则 UML 从目标系统的不同角度出发,定义了用例图.类图.对象图.状态图.活动图.时序图.协作图.构件图.部署图等 9 种图. 1.uml补充 统一建模语言(Unified ...

  2. 最简单直接地理解Java软件设计原则之开闭原则

    写在前面 本文属于Java软件设计原则系列文章的其中一篇,后续会继续分享其他的原则.想以最简单的方式,最直观的demo去彻底理解设计原则.文章属于个人整理.也欢迎大家提出不同的想法. 首先是一些理论性 ...

  3. 这样学BAT必面之软件设计原则,还不会就是我的问题

    学习设计原则是学习设计模式的基础.在实际开发过程中,并不要求所有代码都遵循设计原则,我们要考虑人力.时间.成本.质量,不能刻意追求完美,但要在适当的场景遵循设计原则,这体现的是一种平衡取舍,可以帮助我 ...

  4. 强大的模板引擎开源软件NVelocity

    背景知识NVelocity(http://sourceforge.net/projects/nvelocity )是从java编写的Velocity移植的.net版本,是java界超强的模版系统,.n ...

  5. 最简单直接地理解Java软件设计原则之单一职责原则

    理论性知识 定义 单一职责原则, Single responsibility principle (SRP): 一个类,接口,方法只负责一项职责: 不要存在多余一个导致类变更的原因: 优点 降低类的复 ...

  6. 从编译器对指令集的要求看API设计原则

    摘要:最近看<计算机体系结构:量化研究方法(第五版)>,发现指令集设计中的一些原则,对API设计也同样适用,给大家分享一下. 本文中的所有内容来自工作和学习过程中的心得整理,如需转载请注明 ...

  7. 最简单直接地理解Java软件设计原则之接口隔离原则

    理论性知识 定义 接口隔离原则, Interface Segregation Principle,(ISP). 一个类对应一个类的依赖应该建立在最小的接口上: 建立单一接口,不要建立庞大臃肿的接口: ...

  8. 最简单直接地理解Java软件设计原则之依赖倒置原则

    理论性知识 定义 依赖倒置原则,Dependence Inversion Principle (DIP) 高层模块不应该依赖低层模块.二者都应该依赖其抽象. 抽象不应该依赖细节,细节应该依赖抽象. 针 ...

  9. C#软件设计——小话设计模式原则之:依赖倒置原则DIP

    前言:很久之前就想动笔总结下关于软件设计的一些原则,或者说是设计模式的一些原则,奈何被各种bootstrap组件所吸引,一直抽不开身.群里面有朋友问博主是否改行做前端了,呵呵,其实博主是想做“全战”, ...

随机推荐

  1. Python机器学习笔记 使用scikit-learn工具进行PCA降维

    之前总结过关于PCA的知识:深入学习主成分分析(PCA)算法原理.这里打算再写一篇笔记,总结一下如何使用scikit-learn工具来进行PCA降维. 在数据处理中,经常会遇到特征维度比样本数量多得多 ...

  2. 【Vue.js】vue基础: 3种Class和Style绑定语法

    凡是用到了v-bind,那就一定有变量的存在,下面是三种语法的展示: 1. 对象语法: v-bind:class="{active: isActive, 'text-danger': has ...

  3. 微软正式开源Blazor ,将.NET带回到浏览器

    微软 ASP.NET 团队近日正式开源了  Blazor ,这是一个 Web UI 框架,可通过 WebAssembly 在任意浏览器中运行 .Net . Blazor 旨在简化快速的单页面 .Net ...

  4. PHP基础:MYSQL数据库操作

    1.连接到数据库: · 面向对象的方法: $db = new mysqli('hostname', 'username', 'password', 'dbname'); · 面向过程的方法: $db ...

  5. java-初识Properties

    1.通过代码了解一哈: package com.etc; import java.io.File; import java.io.FileInputStream; import java.io.Fil ...

  6. vuex最详细完整的使用用法

    来自:https://blog.csdn.net/qq_35430000/article/details/79412664#commentBox  github仓库地址:https://github. ...

  7. Dynamics 365测试和启用邮箱时候一直显示“安排电子邮件配置测试”怎么办?

    摘要: 本人微信公众号:微软动态CRM专家罗勇 ,回复284或者20181125可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me!我的网站是 www.luoyong.me ...

  8. 2019年1月份A项目面试纪要

    2019年1月份A项目面试纪要 本周二(1月22号),笔者接到了A项目的电话面试.这个面试来自A项目的客户,客户的后勤模块的几个顾问组成阵容强大的面试官团队.参加这个面试,让笔者感触良多,自己虽然在S ...

  9. JPasswordField密码框,JList列表框

    [JPasswordField密码框] //导入Java类 import javax.swing.*; import java.awt.*; import java.awt.event.ActionE ...

  10. Django 如何让ajax的POST方法带上CSRF令牌

    问题 大家知道,在大前端领域,有一种叫做ajax的东东,即“Asynchronous Javascript And XML”(异步 JavaScript 和 XML),它被用来在不刷新页面的情况下,提 ...