http://javatar.iteye.com/blog/706098

最近给团队新人讲了一些设计上的常识,可能会对其它的新人也有些帮助,把暂时想到的几条,先记在这里。

API 与 SPI 分离

框架或组件通常有两类客户,一个是使用者,一个是扩展者。API (Application Programming Interface) 是给使用者用的,而 SPI (Service Provide Interface) 是给扩展者用的。在设计时,尽量把它们隔离开,而不要混在一起。也就是说,使用者是看不到扩展者写的实现的。

比如:一个 Web 框架,它有一个 API 接口叫 Action,里面有个 execute() 方法,是给使用者用来写业务逻辑的。然后,Web 框架有一个 SPI 接口给扩展者控制输出方式,比如用 velocity 模板输出还是用 json 输出等。如果这个 Web 框架使用一个都继承 Action 的 VelocityAction 和一个 JsonAction 做为扩展方式,要用 velocity 模板输出的就继承 VelocityAction,要用 json 输出的就继承 JsonAction,这就是 API 和 SPI 没有分离的反面例子,SPI 接口混在了 API 接口中。

合理的方式是,有一个单独的 Renderer 接口,有 VelocityRenderer 和 JsonRenderer 实现,Web 框架将 Action 的输出转交给 Renderer 接口做渲染输出。

服务域/实体域/会话域分离

任何框架或组件,总会有核心领域模型,比如:Spring 的 Bean,Struts 的 Action,Dubbo 的 Service,Napoli 的 Queue 等等。这个核心领域模型及其组成部分称为实体域,它代表着我们要操作的目标本身。实体域通常是线程安全的,不管是通过不变类,同步状态,或复制的方式。

服务域也就是行为域,它是组件的功能集,同时也负责实体域和会话域的生命周期管理, 比如 Spring 的 ApplicationContext,Dubbo 的 ServiceManager 等。服务域的对象通常会比较重,而且是线程安全的,并以单一实例服务于所有调用。

什么是会话?就是一次交互过程。会话中重要的概念是上下文,什么是上下文?比如我们说:“老地方见”,这里的“老地方”就是上下文信息。为什么说“老地方”对方会知道,因为我们前面定义了“老地方”的具体内容。所以说,上下文通常持有交互过程中的状态变量等。会话对象通常较轻,每次请求都重新创建实例,请求结束后销毁。简而言之:把元信息交由实体域持有,把一次请求中的临时状态由会话域持有,由服务域贯穿整个过程。

在重要的过程上设置拦截接口

如果你要写个远程调用框架,那远程调用的过程应该有一个统一的拦截接口。如果你要写一个 ORM 框架,那至少 SQL 的执行过程,Mapping 过程要有拦截接口;如果你要写一个 Web 框架,那请求的执行过程应该要有拦截接口,等等。没有哪个公用的框架可以 Cover 住所有需求,允许外置行为,是框架的基本扩展方式。这样,如果有人想在远程调用前,验证下令牌,验证下黑白名单,统计下日志;如果有人想在 SQL 执行前加下分页包装,做下数据权限控制,统计下 SQL 执行时间;如果有人想在请求执行前检查下角色,包装下输入输出流,统计下请求量,等等,就可以自行完成,而不用侵入框架内部。拦截接口,通常是把过程本身用一个对象封装起来,传给拦截器链,比如:远程调用主过程为 invoke(),那拦截器接口通常为 invoke(Invocation),Invocation 对象封装了本来要执行过程的上下文,并且 Invocation 里有一个 invoke() 方法,由拦截器决定什么时候执行,同时,Invocation 也代表拦截器行为本身,这样上一拦截器的 Invocation 其实是包装的下一拦截器的过程,直到最后一个拦截器的 Invocation 是包装的最终的 invoke() 过程;同理,SQL 主过程为 execute(),那拦截器接口通常为 execute(Execution),原理一样。当然,实现方式可以任意,上面只是举例。

重要的状态的变更发送事件并留出监听接口

这里先要讲一个事件和上面拦截器的区别,拦截器是干预过程的,它是过程的一部分,是基于过程行为的,而事件是基于状态数据的,任何行为改变的相同状态,对事件应该是一致的。事件通常是事后通知,是一个 Callback 接口,方法名通常是过去式的,比如 onChanged()。比如远程调用框架,当网络断开或连上应该发出一个事件,当出现错误也可以考虑发出一个事件,这样外围应用就有可能观察到框架内部的变化,做相应适应。

扩展接口职责尽可能单一,具有可组合性

比如,远程调用框架它的协议是可以替换的。如果只提供一个总的扩展接口,当然可以做到切换协议,但协议支持是可以细分为底层通讯,序列化,动态代理方式等等。如果将接口拆细,正交分解,会更便于扩展者复用已有逻辑,而只是替换某部分实现策略。当然这个分解的粒度需要把握好。

微核插件式,平等对待第三方

大凡发展的比较好的框架,都遵守微核的理念。Eclipse 的微核是 OSGi, Spring 的微核是 BeanFactory,Maven 的微核是 Plexus。通常核心是不应该带有功能性的,而是一个生命周期和集成容器,这样各功能可以通过相同的方式交互及扩展,并且任何功能都可以被替换。如果做不到微核,至少要平等对待第三方,即原作者能实现的功能,扩展者应该可以通过扩展的方式全部做到。原作者要把自己也当作扩展者,这样才能保证框架的可持续性及由内向外的稳定性。

不要控制外部对象的生命周期

比如上面说的 Action 使用接口和 Renderer 扩展接口。框架如果让使用者或扩展者把 Action 或 Renderer 实现类的类名或类元信息报上来,然后在内部通过反射 newInstance() 创建一个实例,这样框架就控制了 Action 或 Renderer 实现类的生命周期,Action 或 Renderer 的生老病死,框架都自己做了,外部扩展或集成都无能为力。好的办法是让使用者或扩展者把 Action 或 Renderer 实现类的实例报上来,框架只是使用这些实例,这些对象是怎么创建的,怎么销毁的,都和框架无关,框架最多提供工具类辅助管理,而不是绝对控制。

可配置一定可编程,并保持友好的 CoC 约定

因为使用环境的不确定因素很多,框架总会有一些配置,一般都会到 classpath 直扫某个指定名称的配置,或者启动时允许指定配置路径。做为一个通用框架,应该做到凡是能配置文件做的一定要能通过编程方式进行,否则当使用者需要将你的框架与另一个框架集成时就会带来很多不必要的麻烦。

另外,尽可能做一个标准约定,如果用户按某种约定做事时,就不需要该配置项。比如:配置模板位置,你可以约定,如果放在 templates 目录下就不用配了,如果你想换个目录,就配置下。

区分命令与查询,明确前置条件与后置条件

这个是契约式设计的一部分,尽量遵守有返回值的方法是查询方法,void 返回的方法是命令。查询方法通常是幂等性的,无副作用的,也就是不改变任何状态,调 n 次结果都是一样的,比如 get 某个属性值,或查询一条数据库记录。命令是指有副作用的,也就是会修改状态,比如 set 某个值,或 update 某条数据库记录。如果你的方法即做了修改状态的操作,又做了查询返回,如果可能,将其拆成写读分离的两个方法,比如:User deleteUser(id),删除用户并返回被删除的用户,考虑改为 getUser() 和 void 的 deleteUser()。 另外,每个方法都尽量前置断言传入参数的合法性,后置断言返回结果的合法性,并文档化。

增量式扩展,而不要扩充原始核心概念

参见:谈谈扩充式扩展与增量式扩展

转载于:https://www.cnblogs.com/qyx2019/p/11024579.html

一些设计上的基本常识相关推荐

  1. 一些设计上的基本常识(转载)

    原文地址:http://code.alibabatech.com/blog/experience_886/software_design_general_knowledge.html 最近给团队新人讲 ...

  2. 一些设计上的基本常识 - 梁飞

    最近给团队新人讲了一些设计上的常识,可能会对其它的新人也有些帮助, 把暂时想到的几条,先记在这里. API与SPI分离 框架或组件通常有两类客户,一个是使用者,一个是扩展者,  API(Applica ...

  3. 推理芯片的性能建立在优化的存储子系统设计上

    推理芯片的性能建立在优化的存储子系统设计上 Inference chip performance builds on optimized memory subsystem design 好的推断芯片可 ...

  4. 固态硬盘驱动器在设计上有个安全漏洞 易导致数据损毁

    近年来,出于对提升系统运行速度的渴求,传统机械硬盘(HDD)的市场正被更高速的固态硬盘驱动器(SSD)所蚕食.尽管很多用户仍在采用 SSD 系统盘 + HDD 仓库盘的组合,但后者被淘汰也只是时间问题 ...

  5. R语言ggplot2可视化指定保存到pdf的图像的具体尺寸、保证缩放的一致性:使得绘图元素(文本、点大小等)在设计上都具有相同的绝对大小、设置全局数据点大小、主题格式、设置图像保存的具体尺寸

    R语言ggplot2可视化指定保存到pdf的图像的具体尺寸.保证缩放的一致性:使得绘图元素(文本.点大小等)在设计上都具有相同的绝对大小.设置全局数据点大小.主题格式.设置图像保存的具体尺寸 目录

  6. let/var——事实上var的设计可以看成JavaScript语言设计上的错误. 但是这种错误多半不能修复和移除, 以为需要向后兼容.||将let看成更完美的var

    事实上var的设计可以看成JavaScript语言设计上的错误. 但是这种错误多半不能修复和移除, 以为需要向后兼容. 大概十年前, Brendan Eich就决定修复这个问题, 于是他添加了一个新的 ...

  7. 设计上如何避免EMC问题

    最近经常被问到EMC相关的问题,比如怎么设计才能避免EMC的问题,我想经常关注高速先生的同鞋们有机会肯定也会问到这个问题.首先这是一个系统 性的问题,不是那么好回答,尤其是对于聚焦在高速信号这个领域而 ...

  8. android flux 与mvp,使用 MVP 时在设计上的考量

    在"FluxJava: 给 Java 使用的 Flux 库"这篇文章中提到,设计中使用 MVP 最大的问题,是会让不同的画面形成一组.一组的 Class,但各组之间是独立的.MVP ...

  9. Lachesis Shield 设计上的抉择

    最近有很多朋友和同学跟我谈起 Lachesis Shield 设计上的一些问题.我想我需要总结一下我的设计策略,虽然这是个看起来简单得不能再简单的工具. 我面临的选择: 1 界面位置 显然,有很多位置 ...

最新文章

  1. cmd查看python版本-在cmd中查看python的安装路径方法
  2. [专栏目录]-Crypto学习笔记目录
  3. 霸榜各大CV任务榜单,Swin Transformer横空出世!
  4. 使用resNet网络 进行图像分类(jupyter notebook)
  5. dom 生成图片和链接生成二维码
  6. W3c 中文 文档,很不错
  7. jieba分词,构建词典
  8. 大庆油田真正解决了吃饭问题
  9. Ubuntu 网络配置
  10. 【matlab安装】手把手图文并茂安装matlab2021(win10版)
  11. C# 键盘钩子和鼠标钩子的使用详解
  12. 如何查询硬盘序列号?百度基本都是错的,其实一条命令搞定!
  13. Java聊天室系统的设计与实现(完整源码 sql文件 论文)
  14. libmodbus在ARM linux开发板上使用
  15. 时间复杂度和空间复杂度(超详细)
  16. 【赛题解读】2021 CCF BDCI 基于飞桨实现花样滑冰选手骨骼点动作识别
  17. php workman消息提醒,原生workman实现消息推送
  18. 老鹰-第一次Python笔记
  19. C++ 实验七 多态性与虚函数
  20. 10-1枚举类的使用

热门文章

  1. 免密登录-python
  2. 现实世界中正在用Java解决的难题
  3. 股票分析之主力资金排序分析
  4. thinkphp中I(parm)用法的注意事项
  5. coreseek实时索引更新之增量索引
  6. ESX Server硬件升级步骤
  7. 安装Sql Server 2005 失败一例
  8. Python 代码覆盖率统计工具 coverage.py
  9. Ubuntu有了第一个全球CDN更新源
  10. [Java] 蓝桥杯ADV-213 算法提高 3-2求存款