知识卡片

应对变化的两种方案:变化层稳定层与抽象层实现层

结构图卡

内容

即使正确预测出了变化,还要靠”完美封装变化”才能真正获得可扩展性,业界应对这个问题主要有两类常见模式,各自把新的复杂度转移到了不同的地方。第一类是变化层与稳定层分离:把系统拆成一个不容易变的”稳定层”和一个专门用来兼容各种可能变化的”变化层”,两层之间通过接口连接——典型例子是数据库访问:业务逻辑(稳定层)不直接依赖某个具体数据库的语法,而是通过一层封装的数据访问接口去操作,当底层数据库从MySQL换成Oracle时,只需替换变化层的实现,不用动稳定层的业务代码;但这种做法的复杂度也真实存在,一是怎么判断某个设计点该分到哪一层,本身就没有标准答案;二是两层之间接口怎么设计才能既通用又不过度抽象,是一个需要经验判断的难题,例如同样是”插入并更新已存在记录”这个功能,MySQL用REPLACE INTO语法、Oracle却用MERGE INTO语法,接口设计必须能屏蔽掉这类具体差异。第二类是抽象层与实现层分离,常见落地形式是设计模式,例如装饰者模式(Decorator Pattern):先抽象出一个”组件”(Component)的通用接口,再让”装饰者”(Decorator)持有一个组件并在其基础上叠加新的行为,从而在不修改已有类的前提下动态扩展功能。这类方案的代价在于:抽象层与实现层之间的调用关系、类的角色划分一旦确定下来,就变成了这套方案的固定骨架,后续很难轻易修改——为了获得灵活性,反而要先设计出一套本身并不简单的类结构,把”一个简单的类”拆成了”多个互相配合的类”,这本身就是新增的设计与理解成本。

结构图

flowchart TB
  A["完美封装变化的两种应对方案"]
  A --> B["方案一:变化层/稳定层分离<br/>如数据库访问接口封装"]
  B --> B1["复杂度①:如何划分哪些设计点属于变化层/稳定层"]
  B --> B2["复杂度②:如何设计跨层接口<br/>需屏蔽具体差异(如MySQL REPLACE INTO vs Oracle MERGE INTO)"]
  A --> C["方案二:抽象层/实现层分离<br/>如装饰者模式(Decorator Pattern)"]
  C --> C1["Component:抽象出通用接口"]
  C --> C2["Decorator:持有Component并叠加新行为<br/>不修改已有类即可动态扩展功能"]
  C --> C3["复杂度:类间关系一旦固定难以修改<br/>为获得灵活性,把简单类拆成多个协作类"]

参考来源

- 位置:《从零开始学架构》第06讲《复杂度来源:可扩展性》"完美封装变化"(源文件:_epub-src/OEBPS/text00000.html) - 结论依据:原文分别说明变化层稳定层分离"以不变应万变,将 '变化' 的部分封装隔离起来,从而降低对系统的整体影响"并举数据库访问接口为例,指出难点在于"如何判断某个设计点是否属于变化层""接口如何设计";抽象层实现层分离部分以装饰者模式为例,说明其通过抽象出Component与Decorator类结构来达成扩展目的,同时指出"抽象层和实现层之间的关系一旦确定,就不能轻易改变",直接支撑本卡片结论及结构图划分。 - 原始内容:以不变应万变,是将"变化"的部分封装隔离起来,从而降低对系统的整体影响……以设计模式中的装饰者模式为例,通过增加一个装饰者(Decorator)类来扩展被装饰者(Component)的功能,而不需要修改原有的类……抽象层和实现层之间的关系一旦确定,就不能轻易改变。