知识卡片
其他解耦合模式:模块系统、多代码树拆分与Périphérique反模式
内容
除了[[public滥用会让四种架构风格实质等同用访问修饰符让编译器强制执行架构规则]]里靠单一语言的访问修饰符,还有更进一步的解耦手段。Java生态里的OSGi和Java 9模块系统能进一步区分”public类型”与”对外发布的类型”——比如可以创建一个Orders模块,模块内所有类型都标记public,但只对外公布一小部分供外部调用,等于在包这层封装之上又叠了一层更细粒度的发布控制。另一个选择是把代码彻底拆进不同的代码树,从源代码层面解耦依赖:以端口和适配器为例,可以拆成业务代码(技术和框架无关:OrdersService、OrderServiceImpl、Orders)、Web源代码(OrdersController)、持久化源代码(JdbcOrdersRepository)三棵树,后两者对业务代码有编译期依赖,业务代码对Web和持久化则一无所知,实现上靠Maven/Gradle/MSBuild这类构建工具把它们组织成不同模块或项目——理想情况下甚至可以让每个组件都对应一个独立项目,但这样做往往会带来性能、复杂度和维护性上的代价,有点过于理想化。更常见的折中是只用两棵代码树:业务(领域,内部)和基础设施(外部),基础设施对业务代码有编译期依赖——这也是很多人简化描述端口和适配器架构时采用的方式。但这种两棵树的做法藏着一个隐患,作者称之为”端口与适配器模式中的Périphérique反模式”(借用巴黎那条允许车辆绕城而不进市区的环形公路Périphérique大道打比方):把所有基础设施代码放进同一棵源代码树,很容易让应用里一个区域的基础设施代码(比如Web控制器)直接调用另一个区域的基础设施代码(比如数据库访问),完全绕开领域代码——如果没有设置正确的访问修饰符,这种”绕城而不进城”的隐蔽耦合会更容易发生。本章的核心思想是:脱离具体实现细节的设计再好也难以长久,必须把设计落实到代码树组织和编译期/运行期解耦模式的具体选择上,同时兼顾团队规模、技术水平、时间预算这些现实约束,尽量让编译器替你维护所选的架构风格,并小心防范来自数据结构等其他渠道的隐藏耦合——所有的实现细节,都是关键的。