许式伟架构课Day25接口设计的准则

153 阅读4分钟

聊接口设计前, 需要探讨, 什么是接口, (做事情的时候, 也需要这样, 先问是什么, 再看如何做)

接口在不同的语义环境下, 主要有两个不同含义.

一种是模块的使用界面, 也就是规格, 比如公开的类或函数的原型、和前端进行交互的接口...

另一种是模块对依赖环境的抽象. 这种情况下, 接口是模块与模块之间的契约. 在架构设计中我们也经常也会听到“契约式设计(Design by Contract)” 这样的说法, 它鼓励模块与模块的交互基于接口作为契约, 而不是依赖于具体实现.

模块的使用界面

对于模块的使用界面, 最重要的是KISS原则, 让人一眼就明白这个模块在做什么样的业务.

KISS的全称是Keep it Simple, Stupid, 直译是简单化与傻瓜化. 用土话来说, 就是要”让傻子也能够看得懂”, 追求简单自然, 符合惯例.

模块的环境依赖

接口的另一种含义是模块对依赖环境的抽象, 也就是模块与模块之间的契约.

模块的环境依赖, 也分两种, 一种是使用界面依赖, 一种是实现依赖. 所谓使用界面依赖是指用户在使用该模块的使用界面自然涉及的. 所谓实现依赖则是指模块当前方案中涉及到的组件, 它带来的依赖条件.

在环境依赖上, 我们遵循的是“最小依赖原则”, 或者叫“最少知识原则”, 去尽可能发现模块中多余的依赖.

具体到细节, 使用界面依赖与实现依赖到处置方式往往还是有所不同的.

从使用界面依赖来说, 我们接口定义更多考虑的往往是对参数的泛化与抽象, 以便让我们可以适应更广泛的场景.

从模块实现的角度, 我们环境依赖有两个选择: 一个是直接依赖所基于的组件, 一个是将所依赖的组件所有被引用的方法抽象成一个接口, 让模块依赖接口而不是具体的组件.

这两种方式如何选择呢? 建议大部分情况下应该直接依赖组件, 而不必去抽象它. 如无必要, 勿增实体.

如果我们大量抽象所依赖的基础组件, 意味着我们系统的可配置性(Configurable)更好, 但学习成本也更高.

什么时候该当考虑把依赖抽象化?

其一, 在需要提供多种选择的时候. 比较典型的是日志的Logger组件. 对于绝大部分的业务模块, 都并不希望绑定Logger的选择, 把决策权交给使用方.

但有时候, 在这一点上过度设计也会比较常见. 比如不少业务模块会选择抽象对数据库的依赖, 以便于在MySQL和MongoDB之间自由切换. 但这种灵活性绝大部分情况下是一种过度设计. 选择数据库应该是非常谨慎严谨的行为.

其二, 在需要解除一个庞大的外部系统的依赖时. 有时候我们并不是需要多个选择, 而是某个外部依赖过重.

其三, 在依赖的外部系统为可选组件时, 这个时候模块会实现一个mock的组件, 并在初始化将接口设置为mock组件. 这样的好处是, 除非用户关心, 否则客户可以当模块不存在这个可选的配置型, 这降低了学习门槛.

整体来说, 对模块的实现依赖进行接口抽象, 本质是对模块进行配置化, 增加很多配置选项, 这样的配置化需要谨慎, 适可而止.

结语

接口设计, 分为模块的使用界面和模块的环境依赖.

对于模块的使用界面, 推崇KISS原则, 简单自然, 符合业务表达的惯例.

对于模块的环境依赖, 我们遵循的是最小依赖原则, 尽可能发现模块中多余的依赖.

此文章为3月Day25习笔记, 内容来源于极客时间《许式伟的架构课》, 强烈推荐该课