当前位置: 首页 > 科技观察

没有项目经验的安江,连低耦合和高内聚都不懂……

时间:2023-03-18 13:03:53 科技观察

说完,视频聊天栏另一头的面试官急忙下线。座位上只剩下安江一脸茫然。经过这次采访,安江其实自我感觉还不错。面试官看起来很友好,问的问题也很常规。都是关于TCP/UDP和线程进程通信方式的区别。安江对这种热门的面试题已经很熟悉了,手头的程序员面试书也有些破烂,显然看了很多遍。但面试官的最后一句话,让他觉得有些寒意。一般来说,面试官问的问题不多,主要集中在计算机网络和操作系统的基本原理上。但是到了最后,面试官转移了话题,没有继续深究刚才daemon进程的问题,而是突然问了一个关于编程思路的问题。“你平时写的代码多吗?你知道为什么要低耦合高内聚吗?”这个题比较开放,原理上不难。但对于安江来说,却有些难以回答。因为他不是纯CS专业的,在校期间也没有接触过什么重大项目。所谓作为“负责人”的项目经验,其实无非是小软件或小系统的拼凑而成。“emmm……我一般不会写很多代码,我感觉低耦合就是让代码之间的联系更小,然后高聚合可能就是把相似的功能写在一个函数里……大概是这样的”安江强答了一波,他觉得这种事情太难描述了,脑子里一片模糊。“没了?你们做项目的时候有没有涉及到模块化或者组件化?”面试官自始至终都保持着笑容。“嗯,可能是项目比较小,估计没怎么接触过吧……”我瞬间没了信心。“没关系,谢谢你的时间,我们回去好好学习一下低耦合高内聚。”面试官笑着点了点头,退出了聊天室。这其实是我的一次真实面试经历。刚出去准备工作面试的时候,由于缺乏完整全面的项目经验,连代码的高内聚低耦合都说不清楚。但是,在开始参与大型项目的开发时,往往离不开这六个字的思考和实践。高内聚,低耦合。写过代码的人肯定听说过这个原理,但是要将这六个字解释清楚却并不容易。因为这六个字的思想过于集中,包含了设计模式的几个原则,体现了面向对象的开发思想,甚至可以为软件系统架构的演进提供解决方案和思路。那么这次我们就试着谈谈为什么我们的代码要高内聚低耦合。1凝聚力首先是一个概念上的东西。凝聚力:从功能的角度衡量模块内的连接。耦合度:衡量模块间相互关系程度的指标,取决于模块间接口的复杂程度、调用方式和传递的信息。简而言之,内聚性是指程序可以做什么的相关性。如果这段程序执行的功能相似,比如都是完成文件操作,或者网络请求等,那么这段代码就会被认为是高内聚的。而如果这个程序只是各种操作的混合体,一会儿处理文件,一会儿刷新界面,这样的程序会被认为是低内聚的。内聚最实际的要求是每个模块尽可能独立完成自己的功能,不依赖于模块外的代码。这里的模块其实只是程序的一个细粒度的划分,可以是一个函数,一个类,一个包,也可以是一个空间。一般来说,粒度越大的模块需要的内化程度越高。根据模块内部联系方式的不同,内聚可以分为几种类型,包括逻辑内聚、时间内聚和过程内聚。不同的内聚方法实际上取决于代码的组织方式。比如逻辑内聚就是根据不同的判断条件执行不同的逻辑;temporalcohesion是按照程序执行的时间顺序来组织代码的不同部分。因此,代码的内聚性就是保证每个模块的功能单一,使其作为最基本的组件,被其他程序调用。这样的代码结构更有利于维护和架构升级。2耦合耦合是一个非常普遍的概念,在许多领域都有使用。在一些老项目的开发改造中,往往需要对程序做一些解耦工作。所谓解耦就是降低程序整体的耦合度,提高不同模块之间的内聚性。耦合是一件非常容易且成本不高的事情。耦合就是混乱,关系错综复杂,牵一发而动全身。同样,耦合也可以根据模块之间接触方式的不同分为几种类型。这里有几套耦合方式,试着理解一下。数据耦合:调用模块和被调用模块之间只传递简单的数据项参数。相当于高级语言中的传值。控制耦合:模块之间传递的不是数据信息,而是标志、开关等控制信息,一个模块控制另一个模块的功能。外部耦合:一组模块都访问同一个全局简单变量,该全局变量的信息不通过参数表传递。公共耦合:一组访问同一个全局数据结构的模块称为公共耦合。公共数据环境可以是全局数据结构、共享通信区域、内存的公共占用空间等。如果模块只向公共数据环境输入数据,或者只从公共数据环境中检索数据,这是一个相对松散的公共耦合;如果模块既向公共数据环境输入数据又从公共数据环境中检索数据,则这是一种更紧耦合的公共耦合。耦合其实很好理解。如果把每个模块比作一个齿轮,那么下图就是一个高度耦合的系统。各齿轮相互啮合,一个齿轮的运动会带动其他多个齿轮同时运转。同时,当多个齿轮独立运动时,系统会因相互干扰而锁死。在具体的项目中,这样的情况并不少见。最简单的场景是,当一个对象依赖于另一个对象的实现时,相当于建立了一层耦合。当这种情况增多时,整个系统中的依赖就会变得臃肿复杂。多个对象相互依赖,甚至可能存在多重依赖和循环依赖。这种情况在大型软件系统的迭代中是非常致命的。3解耦既然如此,那么在升级和优化软件架构时就不可避免地要对系统进行解耦。解耦的方式有很多种,可以根据不同语言的特点或者框架本身提供的解决方案来进行。但是这些方案在本质上都有一个共同的目的,就是减少齿轮之间的依赖。降低依赖性很容易。无非就是把齿轮之间的距离拉长一点。你摸不到我,我也摸不到你。这在实际系统中可以表现出来,每个对象并不直接使用其他对象中的资源,包括方法属性等。齿轮是分开的,但显然这样的系统是行不通的。那么各个齿轮之间如何建立联系并传递动力呢?这是一种更通用的方法。由于各个档位不能直接相连,所以给它们加一个中转站就可以了。使用其他东西在其他成对的不同齿轮之间传输消息或运动。这是不是像集中式电话转接线路,所以分布式不是在任何情况下都好用。一般来说,这样的中转站可以是一个服务(moduleService)作为接口,也可以是一个管理器(Manager)。通过这样的中介,在不同的模块之间请求和返回资源。一句话,这个方案就是通过第三方来解耦不同模块/组件之间的对象。所有对象的控制权都交给这个第三方,一个对象需要的外部资源(包括对象、资源、常量数据)通过中间人返回。看看这是不是一种似曾相识的感觉。没错,这就是所谓的IoC容器。4先加载IoC容器,丢了两个名词。IoC容器:InversionofControl,《控制反转》。DI:DependencyInjection,即“依赖注入”。IoC不是一种技术,而是一种思想,一种重要的面向对象编程法则,它可以指导我们如何设计松散耦合的更好的程序。传统应用中,我们主动在类内部创建依赖对象,导致类间耦合度高,难以测试;使用IoC容器,将创建和查找依赖对象的控制权交给了容器,容器注入组合对象,所以对象是松耦合的,这样也便于测试,有利于功能复用,更重要的是,使得整个程序的架构非常灵活。(知乎的解释)其实IoC给编程带来的最大改变不是来自代码,而是来自意识形态,发生了“主从换位”的变化。应用本来就是老大,它主动获取任何资源,但是在IoC/DI的思维中,应用变成了被动的,被动地等待IoC容器创建并注入自己需要的资源。依赖注入是基于IoC的一种更具体的描述,它明确描述了“注入的对象依赖于IoC容器配置的依赖对象”。这听起来很深奥,但概念并不重要。因此,简单来说,IOC就是把一个复杂的系统分解成相互协作的对象。这些对象类封装后,内部实现对外是透明的,从而降低了问题求解的复杂度,并且可以灵活的复用和扩展。低耦合和高内聚是编程中极其重要的思想,它在局部决定了系统的整体发展和演化。说实话,我不是很想明白,但是因为两个月前刚入职,老板让我进行小规模的重构,所以写了一篇文章记录一下。水平有限,继续加油……作者简介:我是暗江,一个迷迷糊糊进大厂的业余码农。分享全栈技术,目标架构师。关注我,一起走向科技的巅峰!本文转载自微信公众号“业余码农”,可通过以下二维码关注。转载本文请联系业余码农公众号。