编写可维护软件的不朽代码随想-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);
        }
    }

这样,其他组件与PlatformServices之间就可以形成松耦合关系。

 

常见反对意见:

1. 因为组件之间是混乱的,所以不可能修复组件依赖

通常在维护时遇到的最明显问题就是各个组件互相纠缠在一起,应该从分析模块是否含有透传调用开始,因为它对可测试性以及准确预测功能方面影响最严重。将组件职责的边界划分清楚,也会提高组件的可分析度和可测试度,例如,含有大量传入调用的模块,说明它们承担了多种职责并且可以被拆分,当它们被拆分后,代码就变得更加易于分析和测试了。

2. 没时间修复组件依赖

我们应当解决对可维护性造成真正影响的问题,当团队发现组件依赖破坏测试性、分析性或者稳定性时,就应该解决它。可以通过测量紧耦合组件所造成的问题上升率,或者增加的维护成本,来巩固你做这件事的理由。

3. 透传代码来自于需求透传

有些架构设计了一个中间层,从一侧(用户界面)收集请求然后打包传给其他层的服务层,只要实现了松耦合,这种层本身不是问题,它应该能够明确区分传入请求和传出请求,该层中接收请求的模块要遵循以下原则:不应该处理请求本身;不应该知道去哪里及如何处理请求。如果满足了这两点,服务层中的接收模块就有一个传入请求和传出请求,而不是将请求传给接收组件中的某个具体模块。

 

SIG评估组件独立性

SIG将组件间的松耦合定义为 组件独立性,独立性按照模块级别进行测量,系统中的每个模块都应该属于一个组件,这里的模块是指代码单元最小集合,通常是一个文件。通过测量模块之间的调用(静态代码分析)来得到它们的依赖关系,将其划分为隐藏代码和接口代码。

隐藏代码由没有传入依赖的模块组成,这些模块只在自己的组件内部互相调用,也可能调用组件外部的其他模块。

接口代码由含有传入依赖的模块组成,包括传入调用和透传调用的模块代码。

SIG根据隐藏代码所占比例作为组件独立性的测试标准,还是分为4星,要到达4星标准,含有传入依赖的模块比例不能超过14.2%

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值