写在前面的话
前端写了好多年,用过了各种各样的组件库,也曾自己写过各种组件。但是对组件这个概念一直都没有过一个很深入了解,所以参考了很多资料,以及结合自己的理解,做归纳和总结。so,才有了这一篇,从什么是组件,到什么是软件中的组件化设计,再到组件设计中有什么优势和挑战,到最后如何构建一套出色的组件系统。每个人的想法都不是一样的,我归纳的可能也并不完善,仅作为大家的参考,也欢迎大家补充。
什么是组件?
这就是我们现实生活中,最形象最贴切的物品了。
图一:是一堆齿轮驱动的系统,每个齿轮都是单独存在的,如果一个齿轮坏了,我们只要替换下来换一个好的就可以了,这个系统又可以运转起来了。
图二:是个人PC的主板,大家也特别熟悉,对于插在主板上的每一个单独功能的组件(例如显卡、CPU等等),如果坏了一个,可以换一个好的,通过插槽替换上去,也可以运转起来。
所以生活中有很多组件的实体,我们看看这些关于组件的定义:
1. 供装配整台机器、构件或元件的零件组合
2. 在电子或机械设备中组装在一起形成一个功能单元的一组元件
3. 组装产品(如书橱或碗橱)时所组合的通常或多或少重复的部分
4. 可被组装或被重新组装的几个部件之一
这些都是我们看得到,摸得到的,下面我们再跳出现实表象,上升到一个更高的层面去看问题(这样的说法也叫抽象)
图三:是一个完整的公司的职能部门图。这个架构由各个职能负责人组成各个部门带动公司运转。我们可以将这个抽象一下。由每个最小的职能负责人都是一个最小组件,由这些小组件组装成了一个部门组件;再由这些部门组件共同驱动了公司这个系统运转。
我们将信息进行汇总和整理,得出如下结论:
1. 从外观和功能上来讲,他们都是一个独立的部分,可能是零件或者是原件
2. 从行为上来讲,他们拥有组装这个属性,可以和其他组件相互组合
3. 可以被重新组装,重复利用
当然我们做软件设计的,也是通过参考现实,抽象到软件设计中,然后根据落地环境,去实现组件化设计思想,前人已经走在了路上,去摸索这块土地,也获得了一些实用的理论,比如基于组件的软件工程(Component-based software engineering,简称CBSE)或基于组件的开发(Component-Based Development,简称CBD)的软件开发范型等等,有兴趣的可以自己去看下。
这些是在软件领域前人对组件的定义:
1. 一个不透明的功能实体,能够被第三方组装,且符合一个构件模型。 — 卡耐基梅隆大学
2. 是软件系统中具有相对独立功能、接口由契约指定、和语境有明显依赖关系、可独立部署、可组装的软件实体。 — 计算机百科全书
3. 是一个组装单元,它具有约定式规范的接口,以及明确的依赖环境。构建可以被独立的部署,由第三方组装。 — 软件构件著作
4. 为自包含的、可编程的、可重用的、与语言无关的软件单元,软件组件可以很容易被用于组装应用程序中。 — 百度百科
5. ......
每个结论针对的都是各自稳定软件领域的抽象,我们还可以看到更多对于组件这个概念的定义。所以我们针对这些结论,做一个总结
组件具有什么样的特性?
1. 高度内聚,不透明 — 不需要关心这个东西怎么搞出来的,知道怎么用就好了
2. 对外以接口契约 — 有说明书,知道这个东西给了需要的就变成你想要的
3. 功能相对独立 — 相对概括的指明这个东西的使用途径
4. 环境依赖 — 对环境的依赖比较重要
5. 可重用 — 可以多次使用
6. 可组装 — 可以和其他东西组成其他的玩意
组件系统中应该有哪些组件?
原子组件:原子级别组件,单一功能,不能继续拆分。
复合组件:多组件组合,组成的能完成某一功能的组件。
插槽组件:为组件的组装完成一个功能而提供的基础设施。
拓展组件:在原有组件上派生出一个新的组件,为原有组件增加新的性能或者更改原有组件的功能。
适配组件:通过适配组件去封装不同组件,保证对外契约一致
有了组件的定义,知道什么是组件,组件有什么特性,以及最后系统中的组件分类大概有哪些。我们认识了组件这个概念,但是接下来,我们怎么去运用组件化的设计思想,去设计我们的系统?
什么是组件设计思想?
将需求场景领域化,将场景领域模块化,以符合系统需求为衡量,以稳定领域最大复用为目的,使其可以通过组合拆分来构建整个系统的独立解决方案
如图:
案例(用前端系统举例):
比如我们需要做一个简单的前端的活动系统,需求要有登录系统、查看活动、创建活动、修改活动、删除活动。
第一步:将需求场景领域化
登录功能 -- 表单提交、查看活动 -- 数据展示、创建活动 -- 表单提交、修改活动 -- 表单提交、删除活动 -- 用户行为。我们将这些功能抽象成3个领域分别为:表单、数据展示、用户行为3个领域。
第二步:将场景领域模块拆分
简单拆分一个表单领域中的例子,我们可以将表单领域拆分出很多模块,比如文本输入模块、密码输入模块、单选模块、多选模块、下拉选择模块等等
第三步:概括共性和分析差异性
用文本输入模块和密码输入模块做例子。下面我们需要概括共性:他们都能做输入。分析差异性:一个输入文本可见,一个输入文本不可见。最后得出结论:设计一个输入文本的组件,对外开放契约,如果你告诉我你需要把输入不可见,我内部就做处理将这些东西遮住。
产出组件:
组件:输入组件
契约:是否需要遮挡输入
功能:输入文本的
领域:表单输入使用
设计意义:
以这样的方式做组件设计,如果整个系统都有这样的稳定领域,那么这些组件的意义就是针对该系统是最好的,不仅满足了需求,还可以最大功能复用。
组件化设计的好处
- 可重用,对于一个功能独立且单一的组件,不需要花费其他资源,可以直接使用组件达到效果
- 拓展和替换,对于系统的维护和更新来说,可以通过基础组件进行拓展,然后可以通过替换组件去为系统进行更新
- 复杂合作可能性,独立拆分模块和组件,已组件拆分组合构建完整系统,使大型项目合作成为可能。
- 提高效率,这包括对软件复杂性更有效率的管理,快速地推向市场,以及更高的生产力(迭代效率),更高的质量等等
组件化设计的挑战
- 开发组件所需要的时间和精力。
开发组件不是单独完成一个功能或者一个页面,如果开发成组件,所需要的时间和精力成本是远远大于只是纯粹完成一个功能的成本的。
- 不可描述需求的开发。
对于软件的需求阶段,在现实开发中,不可能是所有需求都特别明确的,所以对于这些不定性,不可描述需求的开发,对于组件的挑战是很大的,因为领域不稳定,最终可能抽象的组件也是不定且变化的,这样导致的组件变化成本是急剧上升的。
- 可用性和可重用的冲突。
对于系统中最后形成的组件的衡量,不是一个固定标准的。可能组件对于现阶段的可用性已经完全满足,但是对于未来变化和爆发是否需要前瞻性的做预留和拓展,以达到更高一层的可复用的冲突,需要开发者自己衡量的。
- 组件维护。
组件的维护在小型项目还可以忽略,但是在大型项目中,如果维护需要自己一个代码一个代码的去寻找痕迹,那么这样的组件设计开发不理想,反而违背了组件设计最初的梦想。
- 可靠性以及容错机制。
因为组件内部是相对独立的,我们不知道内部的变化,所以衡量一个组件除了是否满足我的需求以外,是否可靠,以及组件不可靠,出现意外的反应机制是否能够准确定位和降级容错(损失一些功能,保证其他功能完善)。
- 持续增长的挑战。
组件开发是为了更高的减轻开发中的各种负担,但是在组件持续的增长中,组件本身也会成为一种负担,对于本身的负担是否有完善的处理机制,可以在持续增长中hold住。
- 组件的规范。
每个攻城狮的开发风格都有各种差异,组件的规范,在这样的合作中就是重中之重,减少多套理解成本。
- 解析组件的“内核”。
对于核心大脑的健壮是整个系统的灵魂。这个不多说,挂了就直接game over!
有挑战,是真实存在的,但是我们可以通过一系列的方法和规范去解决这些挑战,让组件化设计更好的服务我们的系统。
如何设计一套出色的组件系统?
构建组件系统有2个方向:
设计一套符合系统需求的组件
设计一套可以通用的组件
罗列一些不管哪个方向都要考虑的因素:
1.统一收口和管理组件
对于任何一个系统来说,快速迭代和增长中势必会带来组件的爆发式增长,如果不能将组件统一收口,可能会带来组件的重复开发利用率低下(可能和一个组件功能80%相同,只需要拓展一个原子组件就可以解决问题。或者无收口不知道其他地方有个类似可以满足的组件导致二次开发)。其次管理也一样,好的管理可以最大化发挥组件式开发的优势,不善的管理就会带来这样的疑问,"这样的组件化开发,增加了更多的复杂度,还有什么意义?"
2.组件升级和更新,乃至替换做到可控
忘本式迭代,抛弃旧包袱,迎接新时代这是一种很爽的开发体验。但是也会带来,开发一时爽,xxxxx(大家自己体会)。组件每次的升级,在没有完整的方案前,势必要考虑组件的兼容性,否则我每次迭代一个新的组件,使用的组件都不兼容,都需要从头返工手把手替换使用的组件。那就呵呵了。但是有完整灰度方案以后可以实施,比如组件整体升级2.0版本,才能使用新特性,且有足够的组件升级迁移方案保证升级无影响。这样的可控,可以最大化组件开发的优势。
3.容错,核心与非核心组件的容错方案的处理。
容错式,也分很多种方式,在传统window大型系统中,对于核心组件的容错式就特别的低,因为有些是核心组件,比如CPU,丢了这个就不能玩了。但是对于USB插槽,音频输出插槽等等这些容错率就很高,你坏了无所谓,我系统能跑。只是我会断掉你的组件提供的功能。所以组件的容错,需要针对系统本身去评估,无统一方案。
4.安全性,除对外暴露契约影响,对内应自成沙盒
这个就跟浏览器一样,我受影响的东西,只有我自己对外抛出的约定和契约,否则其他东西都不能影响到我。这样才不会导致会有非本身的变动,而让组件自身出现问题,解耦万物,才能达到内心的聚合和独立。
5.文档,组件的自描述,功能和对外契约
没有文档就是没有自我介绍,谁知道你是谁,能干什么,能不能胜任我的解决方案。
6.可信,组件开发授于任何开发者,必须减少风险的可能,以及全面的测试。
组件设计最终的执行者是开发者,如果一个未受评估或者没有全面测试的组件引入整个系统构建中,那么就是引入了一个很大的风险,以后对于这个风险你可能会承受多余自身的时间去把控这个问题。这也是一个合格的软件开发工程师需要评估的问题。
7.适配器和胶水代码:包装更复杂复合通用组件,应通过适配器设计和胶水代码组合,保证符合组件规范
组件的重复利用和组件之间的组装,肯定会带来更多组件的出现和更复杂的组件出现,自然而然会得到更高的复杂度。但是基于本系统的组件规范和开发规范,可以使用更多的胶水代码去适配成和本系统组件一致的模型,这样你我都一样,学习和使用成本都不高。业内很多都是创建一个组件的原始类,定义组件的基础功能和契约,所有的组件都会去继承这个原始类,达到组件的一致性。
8.友好,对开发者更友好,学习成本和使用成本更低廉
在软件开发中,下游消费的是程序员,是将你创建的东西发扬光大的信教徒,你的布道需要更多的友好。否则程序员内心就会出现这样粗俗的声音,“你上来就给老子一棒子,写着写着又给老子一棒子,还玩个蛋!”
9.稳定,一切以稳定为重,远离危险代码
相信每个公司都会安利这样的思维,生产代码是能经历风吹雨打,是能扛得住大山的。你的组件一样必须这样,太多的问题,最终会导致无法维护,成本太高,问题千奇百怪等等,然后你被干掉了。
10.特色,独特的系统有自己的特色和亮点
一般这个都是可选项,毕竟你想的*,可能前人都造了N个,你有什么特色去让别人去选择你,使用你。是因为爱吗?还是因为爱吗?
11.更符合使用者习惯
站在别人的角度去思考问题,才能知道别人需要什么,才能让别人离不开你。
设计一套符合系统经需求的组件(面向需求的组件开发)
第一要素:以需求为中心,以满足需求为衡量标准
1.挖掘符合本系统的组件做基础组件和样本组件。
一个系统的组件系统很少是自己从头搭建,因为成本问题。在业务的快速迭代中,一般都会选择一些提供原子组件的第三方的库和包,通过包装第三方的组件,形成符合本系统需求的组件系统。
2.构建需求组件,适应现有系统的组件模型和需求规范
不同组件定义了自己不同的对外接口契约,千变万化的契约会带来杂、乱、差。不管哪一方的组件,乃至自己的组件,都需要遵守本系统的组件模型规范。
3.可选拓展,在需求可伸缩范围支持拓展和收缩
在需求组件的开发中,对需求的发展,乃至回滚都有很清楚的认识。在这样的基础之上,可以在组件开发中留有可选拓展项,需求伸缩和回滚都能把控。
4.适可而止,不过多设计
一切以需求为准,满足即是最好。
5.自描述性更高,组件设计对需求的耦合性更高,需要更多的描述
因业务的复杂度耦合更多的独特的规则,这样的组件更需要更高的自描述性内容。
设计一套可以通用的组件(面向复用的组件开发)
第一要素:以通用为中心,以抽象共性解决同类问题为衡量标准
1.关联稳定的领域抽象
通用,即是稳定的,形成共识的领域。所以通用组件,也是稳定领域抽象出的组件
2.将组件一般化
一般组件都是为了解决特定问题而产生的应用,将这个应用进行一般化,关联更普遍的业务对象
3.控制复杂度和可读性
越通用的组件,可复用性越高,但是复杂度性对也越高,可读性也越差
4.一致性
统一的接口和契约,乃至统一的对外异常暴露,只有大家越一致,更通用才会成为可能
5.适应性
为组件增加一个配置接口,为组件伸缩提供一种可能
针对组件设计的挑战,我们从未因为有挑战就停止前进的步伐,所以结合前人探索和自己开发中的经验和理解,对于想构建一套出色的组件系统的各位攻城狮提供一个指南和参考。
后记
关于市面上所有前端组件做过分析,确实在通用的场景下解决了我们很高的效率,但是我们了解了组件这个东西本身,再去客观的看所有组件,下面是我发现的一些问题:
- 底层能力不足。基于场景化的组件,只有在适配的场景下去找适当的场景组件,但是一个场景千变万化,虽然最后终究能穷举出所有场景,能否在一种方式中可满足这种变化。
- 组件的替换能力,在市面上的场景组件中很多都是由好多小场景组件组装而成,是否可以在契约相同的方式里,不改动组件本身,通过对外暴露方法,直接替换其中的小组件。
- 拓展和衍生能力,最小的场景组件,是否可以通过一种方式去衍生变种。比如按钮,是否可以暴露“可控补丁”,我可以变成任何形状,直的,方的,圆的,椭圆等等,而不是直接写死,等待迭代。
- 等等.......