【程序设计】一文讲解程序设计目标:高内聚,低耦合

前言

软件设计的目标是高内聚、低耦合

如果代码是高耦合和低内聚的就会出现修改一个逻辑,会导致多处代码要修改,可能影响到多个业务链路,这增加了出bug的业务风险,同时增加了测试回归的范围,导致研发成本增加

耦合和内聚,是我们常挂在嘴边的话,但是大家却说不太清楚,讲不太明白,很难衡量:

  • 什么样的叫高内聚,什么样的叫低耦合?
  • 高内聚要高到什么程度,低耦合要低到什么程度?


 

3.1 耦合的类型


 


 

耦合是描述模块(系统/模块/类/函数)之间相互联系(控制/调用/数据传递)紧密程度的一种度量。

  • 紧耦合:模块之间联系越紧密,耦合性就越强,模块的独立性则越差;
  • 松耦合:模块之间联系越松散,单个模块解决问题的目的越明确,模块的独立性越强。


 

✪ 3.1.1 非直接耦合(Nondirect Coupling)

如果两个模块之间没有直接关系,它们之间的联系完全是通过主模块控制调用来实现的,这就是非直接耦合,这种耦合的模块独立性最强。

class User {
    long userId;
    String userNick;
}

class MessageService {
    void pushMessage(long userId, String message);
}

class UserLoginService {
    void onLoginEvent(long userId) {
        User user = queryUserById(userId);
        String message = user.getUserNick() + "登录成功。";
        messageService.pushMessage(userId, message);
    }
}
}

✪ 3.1.2 数据耦合(Data Coupling)


 

如果一个模块访问另一个模块时,彼此之间是通过数据参数(不是控制参数、公共数据结构或外部变量)来交换输入、输出信息的,则称这种耦合为数据耦合,它是较好的耦合形式。

class MessageService {
    void pushMessage(long userId, String userNick) {
        String message = userNick + "登录成功。";
        doPushMessage(userId, message);
    }
}

class UserLoginService {
    void onLoginEvent(User user) {
        messageService.pushMessage(user.getUserId(), user.getUserNick());
    }
}


 

✪ 3.1.3 印记(引用)耦合(Stamp Coupling)
 

当模块之间使用复合数据结构进行通信时,就会发生印记耦合。

  • 复合数据结构可以是数组、类、结构体、联合体等的引用,通过复合数据结构在模块之间传递的参数,可能会或不会被接收模块完全使用。
class User {
    long userId;
    String userNick;
    // 该属性未被MessageService使用
    int level;
}

class MessageService {
    void pushMessage(User user) {
        String message = user.getUserNick() + "登录成功。";
        doPushMessage(user.getUserId(), message);
    }
}

class UserLoginService {
    void onLoginEvent(User user) {
        messageService.pushMessage(user);
    }
}

印记耦合优点:

  • 把模块A的引用一把传递给模块B,模块B只需要接受少量参数,接口说明简单。

印记耦合缺点:

  • 不必要的参数:模块B可能只使用了模块A中部分的数据;
  • 模块B捆绑了模块A:任何需要用到模块B的地方,都需要先获取到模块A,无法脱离模块A单独使用;
  • 修改可能互相影响:修改模块A或模块B,可能导致对方也需要跟着修改,不符合开闭原则。

印记耦合优化:

增加入参数类型,仅传入模块需要的必要数据,如下:


 

✪ 3.1.4 控制耦合(Control Coupling)

如果一个模块通过传送开关、标志等控制信息,明显地控制选择另一模块的功能,就是控制耦合。

class MessageService {
    void pushMessage(long userId, bool isNewUser) {
        if(isNewUser) {
            doPushMessage(userId, "登录成功。");
        }
    }
}

class UserLoginService {
    void onLoginEvent(User user) {
        messageService.pushMessage(user.getUserId, user.getIsNewUser());
    }
}

数据耦合和控制耦合的主要区别:

  • 在数据耦合中,模块之间的依赖关系非常小,而在控制耦合中,模块之间的依赖关系很高。在数据耦合中,模块之间通过传递数据进行通信,而在控制耦合中,模块之间通过传递模块的控制信息进行通信;

控制耦合优化:

  • 把控制的逻辑放在模块A之中,或增加模块C封装控制逻辑,不然模块B只做某一件独立的事情。


 

✪ 3.1.5 外部耦合(External Coupling)


 


 

外部耦合,是指多个模块同时依赖同一个外部因素(IO设备/文件/协议/DB等),如上图所示:外部耦合与与外部设备的通信有关,而不是与公共数据或数据流有关。

一个模块对外部数据或通信协议所做的任何更改都会影响其他模块,可以通过增加中间模块隔离外部变化来降低耦合度,如下:


 

✪ 3.1.6 共用耦合(Common Coupling)
 


 

共用耦合是指不同的模块共享全局数据的信息(全局数据结构、共享的通信区、内存的公共覆盖区)

public Response loadInitInfo(Request request) {
    // request&response是Commands的全局数据
    Response response = new Response();
    commandExecutor.serial(request, response,
        orderRenderRateLimitCommand,
        renderInitResponseCommand,
        renderEnrichTradeNoCommand,
        renderEnrichItemCommand,
        renderEnrichCombinationCommand,
        renderEnrichPriceCommand
  );
    return response;
}


 

共用耦合的问题:

  • 较难控制各个模块对公共数据的存取,容易影响模块的可靠性和适应性;
  • 使软件的可维护性变差,若一个模块修改了共用数据,则会影响相关模块;
  • 降低了软件的可理解性,不容易清楚知道哪些数据被哪些模块所共享,排错困难。


 

✪ 3.1.7 内容耦合(Content Coupling)

内容耦合在低级语言(汇编)中出现,高级语言从设计上已避免出现内容耦合。

如果发生下列情形,两个模块之间就发生了内容耦合:

  • 一个模块直接访问另一个模块的内部数据;
  • 一个模块不通过正常入口而直接转入到另一个模块的内部;
  • 两个模块有一部分代码重叠(该部分代码具有一定的独立功能);
  • 一个模块有多个入口。

3.2 内聚的类型


 

内聚,是描述一个模块内各元素彼此结合的紧密程度,是从功能角度来度量模块内的联系。

  • 低内聚:模块内的元素的职责相关性低,通常也意味着模块与外部是紧耦合的。
  • 高内聚:模块内的元素的职责相关性强,通常也意味着模块与外部是松耦合的。

通常,解决了耦合的问题,就解决了内聚的问题,反之亦然。
 

✪ 3.2.1 偶然性内聚

偶然内聚,一个模块内的各元素之间没有任何联系,仅是恰好放在同一个模块内,业务的“Util/Helper”类有大量例子。
 

问题的原因:通常是模块名起的过于抽象,导致不同职责的元素都可以放进去,从而引起了低内聚。

问题的解法:将抽象的模块拆解成多个更小的具体模块,例如RetailTradeHelper可以拆为OrderAmountHelper/OrderPaymentParamHelper。
 


 

✪ 3.2.2 逻辑性内聚

逻辑内聚,把几种相关的功能组合在一起,由调用方传入的参数来确定具体执行哪一种功能。

逻辑内聚是一种“低内聚”,某程度上对应了“控制耦合”,它把内部的逻辑处理暴露给了接口之外,当内部逻辑发生变更时,原本无辜的调用方也会受牵连改动。
 

public void syncOrder(Order order, String dist) {
  if(dist == "oc") {
        syncOrder2Oc(order);
    }
    if(dist == "mis") {
        syncOrder2Mis(order);
    }
    if(dist == "tp") {
      syncOrder2Tp(order);
    }
}

✪ 3.2.3 时间性内聚

时间内聚,指一个模块内的组件除了在同一时间都会被执行外,相互之间没有任何关联。


 

✪ 3.2.4 过程性内聚

过程内聚,指一个模块内的组件以特定次序被执行,但相互之间没有数据传递。

✪ 3.2.5 通信性内聚

通信内聚,指一个模块内的组件以特定次序被执行,且相互之间传递和操作相同的数据。


 

✪ 3.2.6 顺序性内聚

顺序内聚,指一个模块内的元素以特定次序被执行,且上一步的输出被下一元素所依赖。
 


 

✪ 3.2.7 功能性内聚

功能内聚,指一个模块内所有组件属于一个整体,完成同一个不可切分的功能,彼此缺一不可。

参考资料

  • 《阿里技术》-
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@来杯咖啡

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值