编写可维护软件的不朽代码随想-7
作者:互联网
架构组件松耦合
有两种构建软件设计的方式:简单到明显没有缺陷;复杂到没有明显的缺陷。
原则
顶层组件之间应该做到松耦合
尽可能减少当前模块中需要暴露给其他组件中模块的相关代码
模块耦合度关注于单个模块对系统其他部分的暴露程度,组件耦合度关注的是一个组件中的模块,对其他组件中模块的暴露程度。
如果从组件层面来考虑,相同组件中模块之间的调用会被认为是一个内部调用,但是如果从模块层面来考虑,就变成了模块之间的耦合。
将组件级别的松耦合称为组件独立,与其相反的称为组件依赖。当发生组件依赖时,组件的内部实现过多地暴露给了依赖它们的其他组件,使得组件不能表现为一个个单独的个体,导致我们很难判断出对一个组件的改动会影响到哪些其他组件。
一个组件内发生的变化只在这个组件内部有效果时,系统更加容易维护。
能够提升可维护性的调用分为两种:
1. 内部调用是健康的,它们的内部逻辑对于外部是隐藏的。
2. 传出调用是健康的,把要做的任务代理给其他组件,创建了一个向外的依赖,将不同的关注点代理给其他组件是一件好事,代理可以在一个组件内部任何地方进行,不必受限于组件内模块的数量。
降低可维护性的调用包括:
1. 传入调用,通过提供一个接口,为其他组件提供功能。组件内的代码应尽可能地封装,避免来自其他组件的直接调用。
2. 透传代码,必须要避免。透传代码既接收传入调用,又同时代理给其他组件。其违反了信息隐藏的原则,将自己的代理(实现)暴露给自己的客户,就像你向一个公司的帮助中心咨询了一个问题,它却没有给出答案,而是直接将问题转给了另一个公司。你就不得不依赖于两个公司来获得答案。从代码角度来看,说明组件之间没有良好的划分职责,不仅难以追溯到请求的整个路径,也难以测试和修改。
透传是透明传输
比如这样的设计:
层与层之间设计的单向依赖,
用户接口——服务层——业务逻辑层——数据抽象层——数据库层
随着时间,对于依赖原则的违反,导致了互相混乱,用户接口可能直接查询到数据库层,或者,服务层跳过业务逻辑层,直接对数据操作。
低组件依赖可以分离维护职责,让测试变得更容易。
使用本原则:
抽象工厂模式,这是经常在实践中使用的,能够成功限制组件对外部暴露接口的设计模式。更多地依赖于约定,而更少依赖于实现细节。
依赖注入框架,也是个很好的方法做到架构组件松耦合。
抽象工厂模式在一个通用的 产品工厂 接口背后,隐藏了具体 产品 创建过程,产品通常不只有一种类型。以下例子:假设有一个名为PlatformServices的组件,实现了对某个云主机平台服务的管理功能,支持两个具体的云主机平台,Amazon AWS和Microsoft Azure,为了能够启动、停止云平台的服务器,以及预定存储空间,为一个云主机平台实现这个接口:
public interface ICloudServerFactory { ICloudServer LaunchComputeServer(); ICloudServer LaunchDatabaseServer(); ICloudStorage CreateCloudStorage(long sizeGB); } public class AWSCloudServerFactory : ICloudServerFactory { public ICloudStorage CreateCloudStorage(long sizeGB) { return new AWSCloudStorage(sizeGB); } public ICloudServer LaunchComputeServer() { return new AWSComputeServer(); } public ICloudServer LaunchDatabaseServer() { return new AWSDatabaseServer(); } } public class AzureCloudServerFactory : ICloudServerFactory { public ICloudStorage CreateCloudStorage(long sizeGB) { return new AzureCloudStorage(sizeGB); } public ICloudServer LaunchComputeServer() { return new AzureComputeServer(); } public ICloudServer LaunchDatabaseServer() { return new AzureDatabaseServer(); } } //这些工厂类会调用特定的AWS和Azure实现类,对服务器和存储返回各自通用的接口类型。PlatformServers组件之外的代码,可以按照如下方式使用接口模块ICloudServerFactory public class ApplicationLanucher { public static void Main(string[] args) { ICloudServerFactory factory; if(args[1].Equals("azure")) { factory = new AzureCloudServerFactory(); } else { factory = new AWSCloudServerFactory(); } ICloudServer computeServer = factory.LaunchComputeServer(); ICloudServer databaseServer = factory.LaunchDatabaseServer(); ICloudStorage cloudStorage = factory.CreateCloudStorage(1000000L); } }View Code
这样,其他组件与PlatformServices之间就可以形成松耦合关系。
常见反对意见:
1. 因为组件之间是混乱的,所以不可能修复组件依赖:
通常在维护时遇到的最明显问题就是各个组件互相纠缠在一起,应该从分析模块是否含有透传调用开始,因为它对可测试性以及准确预测功能方面影响最严重。将组件职责的边界划分清楚,也会提高组件的可分析度和可测试度,例如,含有大量传入调用的模块,说明它们承担了多种职责并且可以被拆分,当它们被拆分后,代码就变得更加易于分析和测试了。
2. 没时间修复组件依赖:
我们应当解决对可维护性造成真正影响的问题,当团队发现组件依赖破坏测试性、分析性或者稳定性时,就应该解决它。可以通过测量紧耦合组件所造成的问题上升率,或者增加的维护成本,来巩固你做这件事的理由。
3. 透传代码来自于需求透传:
有些架构设计了一个中间层,从一侧(用户界面)收集请求然后打包传给其他层的服务层,只要实现了松耦合,这种层本身不是问题,它应该能够明确区分传入请求和传出请求,该层中接收请求的模块要遵循以下原则:不应该处理请求本身;不应该知道去哪里及如何处理请求。如果满足了这两点,服务层中的接收模块就有一个传入请求和传出请求,而不是将请求传给接收组件中的某个具体模块。
SIG评估组件独立性
SIG将组件间的松耦合定义为 组件独立性,独立性按照模块级别进行测量,系统中的每个模块都应该属于一个组件,这里的模块是指代码单元最小集合,通常是一个文件。通过测量模块之间的调用(静态代码分析)来得到它们的依赖关系,将其划分为隐藏代码和接口代码。
隐藏代码由没有传入依赖的模块组成,这些模块只在自己的组件内部互相调用,也可能调用组件外部的其他模块。
接口代码由含有传入依赖的模块组成,包括传入调用和透传调用的模块代码。
SIG根据隐藏代码所占比例作为组件独立性的测试标准,还是分为4星,要到达4星标准,含有传入依赖的模块比例不能超过14.2%
标签:调用,代码,组件,依赖,模块,不朽,编写,随想,public 来源: https://www.cnblogs.com/yorkness/p/14742398.html