组件设计原则和度量方法

news2024/11/15 13:30:58

在日常开发过程中,Spring、Dubbo、Mybatis等都是我们常用的开源框架。当你在使用这些框架时,不可避免需要通过分析源码来理解内部的实现原理。那么,你在翻阅源代码时,有没有想过这些框架的代码结构为什么要这样进行设计和实现呢?背后是否有一些组件设计的原则呢?这就是今天我们要讨论的话题。

我们知道,组件(Component)是设计和规划软件系统代码结构的基本单元,而在代码结构设计上最重要的关注点就是耦合(Coupling)度,用来处理组件与组件之间的关系。这里,我们以Dubbo框架为例进行切入并给出它的组件关系图。Dubbo框架的源码比较复杂,从顶层的代码结构进行梳理,我们可以得到这样的包结构图。


我们看到Dubbo在代码结构上一共包含common、remoting、rpc、cluster、registry、monitor、config和container等8大核心包,它们之间相互依赖构成一个整体。但这里的依赖关系并不是随意设计的,而是使用到了经典的组件设计原则(Component Design Principle)。

什么是组件设计原则?

组件设计原则有时候也称为分包(Package)原则。针对耦合度,在组件设计上包含以下三条设计原则: 

  1. 无环依赖原则

无环依赖原则的英文全称是Acyclic Dependencies Principle,它的含义很明确,就是说组件与组件之间的关联关系中不应该存在环状结构,我们要避免形成循环依赖。

  1. 稳定抽象原则

稳定抽象原则,即Stable Abstractions Principle。该原则认为如果一个组件是稳定的,那么它就应该是抽象的。反之,如果一个不稳定的组件中就不应该包含很多抽象的内容。

  1. 稳定依赖原则

稳定依赖原则,即Stable Dependencies Principle,强调的也是稳定性,认为组件与组件之间的依赖关系应该是有方向的,也就说一个组件只应该依赖于比它更稳定的组件,反之就是不合理的。

从原则的命名上我们也不难看出,组件耦合原则实际上更多关注的是稳定性(Stablility)。那么什么是稳定性呢?在软件系统中,如果某一个包被许多其他的软件包所依赖,也就是具有很多输入依赖关系的包就是稳定的,例如下图中的这个X组件。


而在下图中存在一个Y组件,但我们认为组件Y是不稳定的,因为Y没有被其他的组件所依赖,但Y自身依赖很多别的组件。


现实中的诸如Dubbo这种框架代码中的包结构通常比较复杂,可能很难找到这些一眼就能判断其稳定性的组件,这时候我们就需要借助一些量化标准来对包结构的稳定性进行衡量。让我们一起来看一下。

组件设计原则的度量方法

我们先来看看组件的稳定度如何进行度量,我们可以使用以下公式:

I = Ce / (Ca + Ce)

其中Ca代表向心耦合(Afferent Coupling),表示有多少个组件依赖与这个组件。而Ce代表离心耦合(Efferent Coupling),表示这个组件本身所依赖的组件数量。I代表Instability,即不稳定性,显然它的值处于[0,1]之间。

针对前面介绍的X和Y两个组件,我们可以使用该工作做一个简单计算。不难得出组件X的Ce=0,所以不稳定性I=0,说明它非常稳定。相反,组件Y的Ce=3,Ca=0,所以它的不稳定性I=1,说明它非常不稳定。

另一方面,组件的抽象度也同样存在类似的计算公式:

A = AC / CC

其中A代表抽象度(Abstractness)。AC(Abstract Class)表示组件中抽象类的数量,而CC(Concrete Class)表示组件中所有类的总和,这样通过对比AC和CC就能简单得出该组件的抽象度。

事实上,组件之间都存在一个依赖链,稳定性在该依赖链上具有传递性。下图展示的是一种更常见的场景,沿着依赖的方向,组件的不稳定性应该逐渐降低,稳定性应该逐渐升高。如果已经处于稳定状态的组件就不应该去依赖处于不稳定状态的组件。


另一方面,正如上图所展示的,大多数组件即具备一定的稳定性也表现出一定的抽象度。如果一个组件的稳定度和抽象度都是1,意味着该组件里面全是抽象类且没有任何组件依赖它,那么这个组件就没有任何用处。相反,如果一个组件稳定度和抽象度都是0,那么意味着这个组件不断在变化,不具备维护性,这也是我们不想设计的组件。所以,在稳定度和抽象度之间我们应该保持一种平衡,下图中中间的那个线就是平衡线。在有些资料中,这条平衡线有一个专业的名称,即主序列(Main Sequence),如下图所示。


我们用距离(Distance)的概念来量化这种平衡,距离的计算公式:

D = abs(1 - I - A) * sin(45)

距离的图形化表示参考下图。


通过这些度量方法,我们就可以全面分析组件的稳定度、抽象度以及与主序列之间的平衡关系。

讲到这里,你可能会问,如何能够有效计算出这些度量方法背后的具体量化数据呢?通过人工的方式进行计算显然不可行,这时候就需要引入一些专门的测量工具了。

组件设计原则的测量工具

这里介绍一款组件关系分析的利器:JDepend。JDepend是用来评价Java代码是否遵循组件设计原则的便捷工具,可以给出代码工程中包与包之间的依赖关系,并分析出每个包的稳定和抽象程度以及是否存在循环依赖等。这些指标与前面中介绍的组件设计量化标准保持一致。

使用JDepend时,我们一般加载它为Eclipse提供的插件。安装完JDepend插件之后,在Eclipse中会出现一个“Run JDepend Analysis”菜单。


直接执行命令,就可以得到JDepend的分析结果了。接下来,我们还是以Dubbo为例,来看一下该框架中位于整个依赖关系中心位置的dubbo.rpc,可以看到如下图所示的分析结果。


JDepend给出了四个子页面,分别是所选中的对象、存在循环依赖关系的包、多依赖的包和被依赖的包。从图中,我们看到具体类(CC)、抽象类(AC)、向心耦合(Ca)、离心耦合(Ec)、不稳定性(I)、抽象度(A)和距离(D)等组价设计原则中所介绍的指标数量,同时还使用“Cycle!”用来标识是否包结构是否存在循环依赖。当然,针对这些可视化界面,JDepend还提供了完整的文本结果描述。

同时,JDepend还为我们自动生成了主序列图以及各个包在该图中的分布情况。对于com.alibaba.dubbo.rpc包而言,得到的效果如下图。


在上图中,我们点击某个点,可以看到该点所代表的包结构中的不稳定性、抽象度和主序列之间的距离值。而图中所分布点分为三种颜色,绿色集中主序列线附件,代表在不稳定性和抽象度之间达成了比较好的一种平衡。黑色点位则相对差一下,如果红色点位,则表示设计上出现了问题,需要引起我们的注意。

可以说,任何组件的设计在耦合度上都不可能是完美的,对于复杂的系统而言尤其如此。因此,更多的时候,我们追求的是一种平衡性。组件设计原则为我们追求这种平衡性提供了很好的理论依据和量化标准,我们可以通过工具将这些理论依据和量化标准转化为工程实践,从而更好地指导我们的日常开发工作。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1915219.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自学鸿蒙HarmonyOS的ArkTS语言<五>attributeModifier动态属性和用attributeModifier封装公共组件

【官方文档传送门】 一、抽取组件样式 class MyModifier implements AttributeModifier<ButtonAttribute> {applyNormalAttribute(instance: ButtonAttribute): void {instance.backgroundColor(Color.Black)instance.width(200)instance.height(50)instance.margin(10…

2008年上半年软件设计师【下午题】真题及答案

文章目录 2008年上半年软件设计师下午题--真题2008年上半年软件设计师下午题--答案 2008年上半年软件设计师下午题–真题 2008年上半年软件设计师下午题–答案

数字滚动动画~

前言 数字从0.00滚动到某个数值的动画 实现&#xff08;React版本&#xff09; Dom <div className"number" ref{numberRef}>0.00</div> JS const _initNumber () > {const targetNumber 15454547.69;const duration 1500;const numberElement…

[leetcode]subarray-product-less-than-k 乘积小于K的子数组

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:int numSubarrayProductLessThanK(vector<int>& nums, int k) {if (k 0) {return 0;}int n nums.size();vector<double> logPrefix(n 1);for (int i 0; i < n; i) {logPrefix[i 1] …

E. Beautiful Array(cf954div3)

题意&#xff1a;给定一个数组&#xff0c;可以先对数组进行任意排序&#xff0c;每次操作可以选择一个ai&#xff0c;将它变成aik&#xff0c; 想让这个数组变成一个美丽数组&#xff08;回文数组&#xff09;&#xff0c;求最少操作次数 分析&#xff1a; 先找出相同的数字…

Android liveData 监听异常,fragment可见时才收到回调记录

背景&#xff1a;在app的fragment不可见的情况下使用&#xff0c;发现注册了&#xff0c;但是没有回调导致数据一直未更新&#xff0c;只有在fragment可见的时候才收到回调 // 观察通用信息mLightNaviTopViewModel.getUpdateCommonInfo().observe(this, new Observer<Common…

常用的JVM启动参数

JVM的启动参数有很多&#xff0c;但是我们平常能用上的并不是特别多&#xff0c;这里介绍几个我们常用的&#xff1a; 1. 堆设置&#xff1a; 。 -Xms&#xff1a;设置堆的初始大小。 。.-Xmx&#xff1a;设置堆的最大大小。 2. 栈设置&#xff1a; 。 -XsS&#xff1a;设置每个…

国产大模型第一梯队玩家,为什么pick了CPU?

AI一天&#xff0c;人间一年。 现在不论是大模型本身&#xff0c;亦或是AI应用的更新速度简直令人直呼跟不上—— Sora、Suno、Udio、Luma……重磅应用一个接一个问世。 也正如来自InfoQ的调查数据显示的那般&#xff0c;虽然AIGC目前还处于起步阶段&#xff0c;但市场规模已…

没想到吧,Python print函数也能玩出花!

目录 1、基础打印技巧&#x1f680; 1.1 print()函数入门 1.2 格式化字符串输出 使用f-string 使用str.format() 2、高级格式化选项&#x1f3a8; 2.1 f-string动态插入变量 2.2 使用format方法 3、控制台颜色输出&#x1f308; 3.1 利用ANSI转义码 3.2 使用第三方库…

结束休刊博客真·vlog | 顺便说一下500粉的事

啊&#xff0c;首先是信 ♥亲爱的读者们&#xff0c; 在这个充满数字韵律与代码奇迹的时空里&#xff0c;我满怀激动与感激的心情&#xff0c;提笔写下这封信&#xff0c;宣布一个令人振奋的消息——经过一段时间的休整与充电&#xff0c;我终于要结束这段宝贵的休刊时光&…

Errno2:No such file or directory,在当前文件确实没有该图片,怎么解决?

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

测试和使用Pogo-DroneCAN CANUART串口扩展模块

关键词&#xff1a;Ardupilot&#xff0c;Pixhawk&#xff0c;DroneCAN&#xff0c;CANUART&#xff0c;Serial over DroneCAN&#xff0c;DroneCANUART&#xff0c;UAVCAN&#xff0c;MAVlink&#xff0c;Px4 Keywords&#xff1a;Ardupilot&#xff0c;Pixhawk&#xff0c;D…

c语言数据结构--图综合应用实验——校院导航

实验内容&#xff1a; 面向学校&#xff0c;构建一个校院导游软件。用无向图表示所在学校的校院景点平面图&#xff0c;图中顶点表示主要景点&#xff0c;存放景点的编号、名称、简介等信息&#xff0c;图中边表示景点之间的道路&#xff0c;存放路径距离等信息。该软件具有以…

dledger原理源码分析(四)-日志

简介 dledger是openmessaging的一个组件&#xff0c; raft算法实现&#xff0c;用于分布式日志&#xff0c;本系列分析dledger如何实现raft概念&#xff0c;以及dledger在rocketmq的应用 本系列使用dledger v0.40 本文分析dledger的日志&#xff0c;包括写入&#xff0c;复制…

软件架构之架构风格

软件架构之架构风格 9.3 软件架构风格9.3.1 软件架构风格分类9.3.2 数据流风格9.3.3 调用/返回风格9.3.4 独立构件风格9.3.5 虚拟机风格9.3.6 仓库风格 9.4 层次系统架构风格9.4.1 二层及三层 C/S 架构风格9.4.2 B/S 架构风格9.4.3 MVC 架构风格9.4.4 MVP 架构风格 9.5 面向服务…

力扣 双指针基础

class Solution {public void moveZeroes(int[] nums) {int l 0;//慢指针但先走for (int r 0; r < nums.length; r) {//快指针&#xff0c;遍历次数if (nums[r] 0) continue;//l比r先到&#xff0c;在此处定住l&#xff0c;r继续移动int t nums[l];nums[l] nums[r];num…

上交发布MG-LLaVA,基于多粒度指令调整,横扫视觉大模型榜单

近年来多模态大语言模型(MLLMs)在视觉理解任务中取得了长足进步。然而&#xff0c;大多数模型仍局限于处理低分辨率图像&#xff0c;这限制了它们在需要详细视觉信息的任务中的表现。针对这一问题&#xff0c;上海交通大学的研究团队推出了MG-LLaVA&#xff08;Multi-Granulari…

Animate软件基础:重命名图层或文件夹

默认情况下&#xff0c;Animate 会按照创建顺序向新图层分配名称&#xff1a;图层 1、图层 2&#xff0c;依此类推。为了更好地反映图层的内容&#xff0c;可以对图层进行重命名。 如果需要对图层或图层文件夹进行重命名&#xff0c;请执行下列操作之一&#xff1a; 双击时间轴…

二叉平衡树(左单旋,右单旋,左右双旋、右左双旋)

一、AVL树&#xff08;二叉平衡树&#xff1a;高度平衡的二叉搜索树&#xff09; 0、二叉平衡树 左右子树高度差不超过1的二叉搜索树。 public class AVLTree{static class AVLTreeNode {public TreeNode left null; // 节点的左孩子public TreeNode right null; // 节点的…

MySQL之基本查询(上)-表的增删查改

目录 Create(创建) 案例建表 插入 单行数据 指定列插入 单行数据 全列插入 多行数据 全列插入 插入是否更新 插入时更新 替换 Retrieve(读取) 建表插入 select列 全列查询 指定列查询 查询字段为表达式 为查询结果指定别名 结果去重 where条件 比较运算符 逻辑运…