正交架构 低代码架构 低组件架构 超类架构

 比耦合架构更好的架构 https://yanhaijing.com/program/2021/07/17/coupling-and-composition/

背景

本文用上传组件作为例子,一般业务中的上传组件都有自己的逻辑,现在假设我们有如下多个不同的上传组件:

  • 文件上传组件,通用的上传组件
  • 图片上传组件,有自己的展示逻辑和图片处理等
  • 音频上传组件,有自己的展示逻辑和音频处理等
  • 路径上传组件,有自己的展示逻辑且参数也不同

现在让我们想一下,实现这些逻辑,代码该如何设计,在往下看之前,建议先看一下我的另一篇文章——图解7种耦合关系,下面会用到这里面提到的知识

耦合架构

耦合架构就是把代码都写在一起,这样就实现了公共逻辑只存在一份;然后内部存在很多条件判断,来实现不同的分支逻辑

相信大家都能看出来耦合架构的问题,心里也都有自己的答案,但耦合架构是怎么形成的呢?耦合架构大部分都是业务演进的结果,开始时业务只有一种,当来了新的业务时,可能开人员偷懒了,也可能是缺乏重构的勇气,就慢慢积累了下来,然后尾大不掉,渐渐失去了所有人的控制

耦合架构的优点大概只有节省设计时间了,因为不用设计,业务线性演变即可

耦合架构的问题在于难以维护,不同业务的逻辑紧紧交织在一起,系统复杂度会随着分支的数量指数级增长,一般随着时间的积累,很可能会逐渐失控,演变成下图这样

耦合架构对于改动非常不友好,极有可能牵一发而动全身,改不动,理不清;耦合架构对于修改Bug非常不友好,如同盲人摸象,难以排查;耦合架构对于阅读非常不友好,理清逻辑犹如管中窥豹,错综复杂

耦合架构演进到最后,很可能被弃坑,特别是人员变更,恰巧来了一个新的功能时,极有可能放弃前人的积累,另起炉灶

拷贝架构

耦合架构演进到最后很可能变为拷贝架构,这也是我在项目中看到的问题,同学们告诉我,这个有点复杂哦,建议你拷一份,别修改之前的代码

拷贝架构就是各个组件完全自成一份,每个都包含完成的功能,在写业务之前,就把之前的拷贝一份

拷贝架构的优点就是相互隔离,业务之间的演进不会相互影响

拷贝架构的缺点也很明显,公共逻辑被复制了多份,违反了DRY原则;特别是如果公共部分自身改动的话,可能会有研发效率加倍,或者逻辑不一致问题

超类架构

面对前面的问题,大家会做出不同的改进,超类架构是一个典型的方向,面向对象思维建议用继承来解决泛化问题,通过抽象一个上传基类,可以解决公共逻辑的问题,不同业务的差异,可以通过子类来泛化实现

超类的优点在于解决了泛化和公共的问题,通过抽象继承隔离了两部分逻辑

超类的问题在于,继承本身就脆弱,大部分前端的OOP能力也良莠不齐,存在四不像设计和啥都往超类塞的问题;超类虽然隔离了公共逻辑,但还是会将公共逻辑已接口的方式暴露给子类,子类需要感知父类的设计;父类很难设计的良好,面对发散的业务,后面父类的迭代会越来越痛苦

超类架构依赖于良好的抽象,如果抽象不好,也会存在弃坑的问题,很有可能拷贝一份而放弃了超类的维护

组合架构

组合架构是更适合前端UI组件的架构,通过将页面拆分成功能独立的组件,再将组件组合到一起从而实现页面功能,在我们的例子中,可以将公共逻辑提取为一个功能单一的上传组件即可,其只包含上传逻辑处理,对外提供属性和回调函数

从架构图上来看和超类是相似的,但继承和组合是两个概念,组合是自下而上的,可逆的,上下层组件之间的耦合是松散的;继承是自顶而下的,不可逆的,父子组件之间的耦合是紧密的

组合架构依赖于设计良好的组件,需要遵从单一职责和开闭原则,设计糟糕的组件会让系统变的脆弱,组件要做到高内聚,低耦合,组件要做到面向参数设计,剥离业务

总结

本文通过一个例子,介绍了前端的4种设计模式,希望能够帮助大家,在解耦合的道路上有更多的选择

一般一个模块人人都觉得复杂,却又没人提出改进,那往往是设计存在问题,一般好的设计都是简单的

多子类型业务架构演进 https://yanhaijing.com/program/2021/07/22/decoupling-the-large-subtype/

 

耦合架构

像这种把子类型的逻辑都写在一起的模式,我称其为耦合架构,需要注意的是耦合也十分级别的,同样是耦合松耦合和紧耦合也是不同的,如果对于耦合的分类感兴趣,可以看下我的另一篇文章——图解7种耦合关系

PS:我的另一篇那文章也提到了耦合架构,感兴趣的可以看看——比耦合架构更好的架构

一般听到耦合,第一反应就是解耦,但是先思考一个问题,我们的代码是如何失控的?

系统很有可能是这样演进的,在业务开始时

  • PM提了一个子类型需求M1,M1包含标题和音频
  • PM提了一个新的子类型M2,在M1的基础上多了一个视频
  • PM提了一个新的子类型M3,在M1的基础上多了一个图片

写下第一行代码的同学很快就完成了M1的开发,当面对M2和M3时,优先会考虑复用M1的逻辑,极有可能选择在M1中加上M2和M3的逻辑

整个系统就这样慢慢的演进,随着时间的积累,慢慢走向失控

  • PM提了一个子类型M4
  • PM提了一个子类型M50
  • PM提了一个子类型M100

终于有一天变成了现在这样,也许早就有同学发现了系统的问题,但未能及时解决问题,相信不少业务会有这种年久失修的问题

现在整个系统紧紧耦合在一起,巨量代码和逻辑交织在一起,整个系统蕴藏着巨大危机,开发效率低下,几乎无法并行开发,因为没法解决代码冲突啊;维护风险很高,真正的牵一发而动全身

整个系统已经到了岌岌可危的处境,急需一个人挽狂澜于既倒,扶大厦于将倾

如果想解决问题,首先我们要理清问题,这个系统目前存在如下问题:

  • 新人上手成本(看不懂)
  • PM让我修改旧类型(改不动)
  • PM说这个可以复用另一个(理不清)

但是这些都只是问题导致的结果,导致这些问题的根源是,一个类型的需求,要面对全部类型的逻辑,系统复杂度:O(n^2)

其实前人们也一直在努力解决问题,只是努力的方向是,在现有架构下修修补补,比如总结文档,代码上面进行归类抽象等

正交架构

正交架构的思想其实非常简单,既然要解耦,那就直接把每个子类型的逻辑分开实现不就好了吗,代码上彻底隔离

正交架构其实就是分治思想,分治思想提倡将大的问题,分离成多个小问题,从而分别解决,在多子类型系统中,子类型就是一个绝佳的分治媒介

正交架构带来的直接好处就是,子类型解耦了,子类型内部逻辑是自治的,相互之间没有关联,从而使每个子类型的复杂度降低了,虽然系统里的逻辑和代码量并没有减少,但系统的整体复杂度降低了,在我们这里例子里面,用正交架构替换耦合架构收益如下:

  • 复杂度 n^2 => n,增加子类型时,系统复杂度线性增长
  • 代码量 2个数量级,修改一个子类型时,代码量由万行级别 => 百行级别
  • 分支数量 3个数量级,系统内部逻辑分支由千级别 => 个位数

既然正交架构整么好,那为何不用正交架构替代耦合架构呢?没错,如果是新系统的话,我建议你从一开始就选择正交架构,但对于存量系统就很麻烦,因为有巨大的历史包袱,在这种情况下,如果时间紧迫,我建议先从新类型切到正交架构,历史包袱先保留不动,正可能需要一点小的代码设计才能实现,但我相信难不倒你的

接下来说说正交架构的问题,通过把子类型的代码分开,带来一个明显的问题,在耦合架构中,类型之间的公共逻辑和组件是复用的,但在正交架构中是重复的,特别是对于复杂的逻辑和复杂的组件,问题尤为明显,一旦这部分功能要统一修改时,那可能要在每个子类型都要修改下

这个问题可以通过将公共组件和公共逻辑抽象出来的方法来解决,一般如果2个子类型重复的部分,就应该提取出来,在业务迭代中,如果你想复用另一个类型的功能时,就是抽象的合适时机,一般PM会提醒你这个事情的,PM可能的对话如下

小颜同学,这个新类型的这块就和之前的一样,我就不用在描述了吧 —— 传说中的一句话需求

关于公共组件化,还有一个问题不得不提,有两种抽象公共组件思路,一种是,组件提供配置,不同子类型使用组件时,配置不同开关;一种是,把子类型作为参数,传递到公共组件,组件内部判断子类型实现不同逻辑;对于前一种,我称为纯组件,对于后一种,我称为非纯组件

我发现不少同学在提取组件时,会写出来非纯组件,其实非纯组件只是把代码物理隔离了,逻辑上并没有隔离,在你的公共组件里其实还是一个小型的耦合架构,所以建议大家选择纯组件

除了上面的非纯组件问题,正交架构还有个最大的问题就是组件化是可选的,这其实给拷贝代码提供了可能,让重复代码有继续存在的温床,提取组件这个事情就是一个最佳实践,属于弱约束的事情,而弱约束一般只能通过代码评审发现……

后面可能发生的事情大家都懂了吧

低代码架构

整个系统在正交架构下跑了一段时间,顶住了业务的压力,但我一直在思考有咩有更好的架构,最近低代码如火如荼,多子类型有咩有转低代码的可能?

说来也巧,刚好业务要对系统进行大的改造重构,在这个过程中,我落地了低代码架构,对于多子类型业务,如果其类型之间存在一些相似或重复部分,那么可以考虑低代码架构

对于单个子类型来说,只需要将正交架构中的代码,全部替换为配置,配置可能会对应一套DSL语言,需要搭配一套渲染器,渲染器读取配置,将每个配置渲染成组件库里的组件,就形成了完整的闭环

我发现低代码刚好解决了正交架构系统可能存在的两个问题,低代码架构强制系统必须全部组件化,这就避免了正交架构可选的组件化可能带来的同样逻辑,实现不同问题;同时低代码架构下,强制要求组件面向配置开发,这恰巧杜绝了非纯组件的问题

低代码这一套铺下来,整个系统的复杂度和子类型数量的解耦,整体收益如下:

  • 复杂度:O(N) => O(常数),N是子类型数量,常数和组件数量相关
  • 开发效率:80% ↑,新类型大概率不需要开发了
  • 强制纯组件化

但低代码也不是没有缺点的,首先整个系统的架构会比正交架构复杂,开发难度更大;低代码系统会增加额外的开发工作,比如配置系统,DSL语言和渲染器的开发等;如果遇到系统不支持的组件,需要额外的开发成本,且开发成本会大于这个组件在正交架构下的开发

低代码架构中有几个关键的技术难点,这里简单提一下

  • 绑定数据能力
  • 联动能力
  • 组件可扩展能力
  • DSL可扩展能力
  • 校验如何设计
  • 组件设计原则

如果对低代码架构的实现细节感兴趣,可以继续关注我的后续文章,您的回复和打赏,是我继续写下去的动力

低组件架构

整个系统在低代码架构美好的演进了一段时间,但很快遇到了一些困难

首先是业务的多样性,导致了某些组件的配置爆炸式增加,有几个典型的组件有几十个开关,其自身的复杂度也需要关注了

有些子类型其功能不具有复用性,对于这种,我们扩展了类型组件的支持,也就是给这个子类型开发一个组件,其逻辑都在一起,就和正交架构的实现区别不大了

最大的问题,有些类型,其校验逻辑和联动关系非常复杂,通过配置化来实现这些联动和校验时越来越困难,维护的同学表示很难受

关于上面的问题,我思考了许久,提出了低组件架构,这个名词是我发明的,低组件架构其实是融合了正交架构和低代码架构,博取两家之长,避开两家之短

拿表单来举个例子,我们的程序其实是分成两部分的,首先是组件的渲染,也可以理解为静态UI的展示,这一部分其实是比较容易通过DSL来表示的;在UI背后还有逻辑层,包括组件的联动,校验等逻辑,这一部分逻辑存在DSL困境

其实我们写的代码,比如js,html等就是通用的DSL,既然如此不如换个思路?一个子类型包含哪些组件,组件包含哪些参数,通过低代码架构来实现;组件背后的逻辑层,通过正交架构来实现

低组件架构,融合了两种架构,避免了正交架构中的组件发散问题,同时也避免了低代码架构中的DSL爆炸问题

不过这个架构,我只是做了纸面上的推演,并未落地,纸上得来终觉浅,希望能给同学们一起启发,如果有同学有尝试,欢迎交流

总结

综上,我们介绍了多子类型系统的4种架构,其实4中架构都能实现需求,但其思想却有很大不同,下面同不同层面做个对比

首先从子类型数量和系统复杂度方面来对比下,低组件架构其实是低代码架构的一个变种,所以此处不单独列出

再来对比下,开发人员面对不同架构时的心智模型

通过上面的介绍和对比,相信同学们对4种架构的定义和区别都有了自己的认识,其实架构没有好坏之分,只有适合不适合,下面从我的认知,给大家总结下不同架构适合的不同业务场景

架构适应场景
耦合架构 无,除非你想离职了,不过害人终害己,天道好轮回
正交架构 子类型小于30个的场景,子类型之间区别很大时
低代码架构 逻辑不重,子类型较多时,子类型迭代较快
低组件架构 同上,但逻辑较重时

 

posted @ 2021-08-26 17:57  papering  阅读(468)  评论(0编辑  收藏  举报