架构误区系列 13:令人迷惑的继承
继承是很多面向对象语言都有的特性,比如 C++、Java 都有继承的语法。通过继承,可以将同类对象的共同特性抽象并封装到父类中,可以让代码结构更清晰、更简洁。但是,继承不是银弹,如果错误的使用继承特性,反而会对代码或模型的架构带来负面的影响。
最近在外汇风险管理能力的建设中,有这么一个例子:
我们的业务是全球性的业务。由于监管等原因,业务需要按照站点隔离。现状我们对于站点的定义是租户+主体(用两个属性标识一个站点这是历史原因,这里不展开)。所以,在 Review 风险模型的时候,我发现数据架构定义了一个 BaseModel 的模型,里面包括主体和租户两个属性。后续的模型,不论是配置类的还是交易类的,包括规则模型、风险模型、指标模型等,都继承这个 BaseModel。
看起来好像没什么问题,有了这个继承关系,至少不需要在每个模型里面都手工增加租户和主体这两个字段了。但是,品下来味道总觉得哪里不对。。。
首先,在风险管理中,有规则模型、指标模型、事件模型、预警模型、处置模型,这些模型之间是相互独立的,按照微服务的理念这些模型未来都可以归属不同的系统。这些在独立系统中没有任何共性的模型继承一个公共的基类,在逻辑上并不自洽。
其次,具体到站点这个概念上,从业务的本质来看,是每个模型都需要一个站点的属性做数据隔离。而由于历史的原因,站点这个概念由两个属性共同来标识。这个妥妥的是领域建模中的值对象的概念,就好比国家/省份的概念一样。所以更合理的做法应该是定义一个站点的值对象模型,其他模型引用这个值对象。
所以,对于信息架构和应用架构的设计,我们一定要回归的到业务的本质上。面向对象里面的对象,是客观世界中存在的实体在信息系统中的映射。只有和业务概念相匹配一致,建模才有其生命力,才能提升系统的质量和可延展性。不要单纯为了减少代码而做过度或错误的架构抽象。
归属统一类别的,继承同一个基类;有相同行为的,实现同一个接口;有相同属性的,引用同一个对象。继承、实现、组合、聚合、引用,都有其严格的业务含义,首先要理解其中的区别,然后再想清楚业务本质,最后再选择合适的关系。
到这里,好像明白了 Java 为啥没有多重继承了。一个子类有两个父亲,分别可以归属到两类实体中,听着就有一点别扭。但是接口就不一样了,一个对象有多个行为,那再正常不过了。
同样,联想到我们在交易里面的定义。对于一个交易,除了有站点信息外,我们还有用户信息 userId、单据号 orderId、外部单据号 requestId、单据状态 status、创建事件 createTime、最后修改事件 updateTime,我们在定义单据模型的时候,会将这些字段抽象到一个 BaseOrder 类中,所有的订单类继承 BaseOrder。这个设计是否合理?背后体现的业务逻辑是啥?
版权声明: 本文为 InfoQ 作者【agnostic】的原创文章。
原文链接:【http://xie.infoq.cn/article/893e7ba3601feb0d3d43b5098】。文章转载请联系作者。
评论