代码精进之路-设计原则

0bf7b365b28c6173134652241f219c30.png

设计原则是前辈的总结,为后来人提供经验,写出更好的代码,降低系统复杂度,提高代码的稳定性,可维护性。

有时候你觉得这个方案这样设计也可以,那样设计也没问题,犹豫不决,这时不妨参考下设计原则,也许你心中就有了方向。

一、高内聚低耦合

1、内聚性

什么是内聚?

一个模块内部元素彼此结合的紧密程度。

这里的模块可大可小,可以指一个函数,可以指一个类,可以指一个接口,可以指一个包,可以指一个模块,可以指一个子系统。

那么在不同的模块上下文中元素也就有了不同的含义,函数的元素就是代码了,类和接口的元素就是 属性和函数(方法)了,包的元素就是类、接口了,模块的元素就是包、命名空间等,子系统的元素就是模块了。

所以判断一个模块(函数、类、包、子系统)内聚性的高低,最重要的是关注模块的元素是否都忠于模块的职责,简单来说就是不能挂羊头卖狗肉。

职责这个词在  ”单一职责原则“ 中也出现了,这个职责并不是只能干一件事情,每一个职责下面可能有多个事情,但是它们一定是相关的,紧密结合的事情。 不相关的事情最好不要放到一个职责中,这就违背了单一职责原则,这就产生了低内聚性。

你是个开发人员,但是老板把需求、测试、运维的事情全归到你的职责下,你觉得正常吗?

内聚的分类:偶然内聚,逻辑内聚、时间内聚、过程内聚、信息内聚、顺序内聚、功能内聚

2、耦合性

什么是耦合?

耦合是程序模块相互之间的依赖程度。

这里的模块和内聚性中的模块是同一个概念,依赖就是一个模块用到了另一个模块的元素,具体体现就是用了另一个模块的接口和类的属性和方法。

耦合的分类:

1)无耦合,一般发生在非常底层的模块,这意味着它对其它模块完全不依赖,这就像一个国家不对外国开放,关闭国门,完全自给自足。这就意味着无法复用代码,需要自己造轮子,所以在应用的上层模块基本是不可能发生无耦合的情况的,在非常底层的模块是有可能发生的。

2)消息耦合,接口方法调用(local或者remote)无输入参数也没有输出参数

也就是模块之间的耦合关系表现在消息传递上。这里的消息随着模块的不同而不同。

这是一种耦合程度很低的耦合,是比较理想的耦合。

3)基本数据耦合,接口方法调用,入参和出参都是基本数据类型

也就是方法的入参和出参数据类型为基本的 interger,double,string等jdk自带的数据类型

4)数据结构耦合,接口方法调用,同时入参和出参使用了自定义的数据结构DTO等

5)控制耦合

意思就是调用方可以通过入参的变化控制被调用方函数内部的行为分支

6)外部耦合

两个模块依赖相同的外部数据格式、通信协议来交互,比如广泛使用的XML、JSON、HTTP等

7)全局耦合

两个模块之间依赖了相同的常量类、全局变量、单例对象属性等

8)内容耦合

一个模块依赖了另一个模块的内部内容,典型的就是 直接依赖了对方的public成员变量。

这是比较差的一种耦合,破坏了面向对象的封装性,就是我们平时的pojo对象没有set和get方法,直接public了成员变量,让对方使用。

3、高内聚低耦合

这可能是个程序员都知道的原则,但是真正做到并不容易。

无论是”低内聚“还是”高耦合“,其本质都是不稳定,不稳定就会带来工作量,带来风险,所以我们应该做到”高内聚,低耦合“。

这就对模块职责的划分提出了更高的要求,通过合理的划分模块职责,达到模块内聚性高,交互接口少(耦合低),写到这想起了DDD,这并不是一件容易的事情,需要设计者对业务非常熟悉才行,他熟知业务的分界点应该划到哪里才内达到 高内聚 低耦合的效果。如果你不是很熟悉业务往往需要随着对业务的深入理解,再不断重构才能达到理想的效果。

4、服务拆分

假如下图代表了一个单体应用包括的所有业务,接下来要对其进行微服务拆分

0307b8be6115ebdd43672c349adccbf1.png

下面的划分边界可能是最理想的了,是我们追求的目标

ed9fa487f315dd70ff349fd1b950c87e.png

下面的划分比较差一点,明显内聚性受到了一定的破坏,耦合度没有达到最低,这就需要随着业务的深入,通过不断重构调整边界来靠近理想的目标

506be6fdfcc60d5a2400eb3e27960bf5.png

下面这种可能是最差的划分了,完全破坏了高内聚,低耦合的目标,这样的划分不但引入了微服务的复杂度,而且它的收益要低于单体应用,这种划分往往带来负面效果

5dc3b2b2e6515a7779437646285429bd.png

二、设计原则

1、SOLID原则+复用原则+迪米特法则

1)SRP=single   responsibility principle : 单一职责原则

四狗  润四炮瑟比了忒   破瑞色剖

这个原则的关键在于对职责的理解,它并不是说只能做一件事情,一个职责可以包括多件相关的事情,事情之间是内聚的。

2)OCP=open  close principle  :开闭原则

偶喷  可露紫  破瑞色剖

对扩展开放,对修改关闭。

这个原则是我们追求的最终目标,它也是其它原则追求的目标。

中间件和框架多多少少对一些功能和组件实现开闭原则,比如说 Spring通过接口和注解实现开闭原则,dubbo通过接口和SPI实现开闭原则,可以看出都是预留接口,然后通过一定的机制替换或者增强原有的逻辑

3)LSP = Liskov Substitution principle :里氏替换原则

利斯科夫   撒不死替身   破瑞色剖

这个原则用来指导类的继承的使用。用子类替换父类后代码是否能达到同样的效果不出任何差错。

一个比较保险的方案就是 只继承抽象类和接口,只实现抽象方法,不要覆盖父类的非抽象方法。

还有一个方案就是使用组合聚合来代替继承。

4)ISP=interface  segregation principle:接口隔离原则

伊特粉丝  塞戈瑞根神  破瑞色剖

如何对类的调用者隐藏隐藏类的某些public方法?答案就是将这个类的接口从一个大的接口,划分成多个小的接口,然后针对不同的调用者使用不同的接口。

比如    IA接口有10个方法,它的实现类为Aimpl。

有2个不同角色的调用者,它们关注的方法不同,但是因为只有一个大的IA接口,就没有办法进行方法隐藏。

这时可以将IA接口分成  IA1,IA2 ,Aimpl同时实现IA1和IA2, 对角色1返回IA1接口,对角色2返回IA2接口,这就达到了隐藏Aimpl类中public方法的目的。

5)DIP=dependency inversion principle:依赖倒置原则

这个原则的应用典型就是 Spring的依赖注入,层与层之间的实现不直接进行依赖,我们依赖抽象:接口。

另一个让老吕重新认识这个原则的就是DDD多层架构(洋葱架构、六边形架构、COLA架构)中都要求  依赖只能由外层依赖内层,而领域模型 domain层是整个架构的最内层,这就是说 domain层不能向外依赖 持久化层,而在实际的工程中,持久化层确实是domain的下层,这就不对了,怎么解决?通过DIP解决的,domain层通过抽象并拥有db相关的gateway接口,持久化层通过依赖并实现gateway接口,从而实现了依赖逆转,实现了外层依赖内层的原则。

6)CARP = 组合聚合复用原则

组合聚合优先于继承,原因就是继承容易出现违反 里氏替换原则问题,另外继承的改动对子类的影响范围较大。

7)迪米特法则

这个原则是说不和陌生人说话,尽量通过朋友的转发来解决和陌生人的通信问题。这我想起了ESB 企业服务总线架构模式。达到的目标就是通过第三方降低模块之间的耦合度。这就涉及到谁是朋友,谁是陌生人的问题,还需要研究,以后再细说。

2、DRY

DRY= do not  repeat yourself

避免重复代码,可以结合三次原则考虑

3、YAGNI

you  not  need  it

防止过度设计,根据项目实际情况,考虑成本效率收益,在合适的时机采用合适的方案。

4、 Rule  of  Three   三次原则

这个也是考虑了成本效率与收益,当重复代码出现第三次的时候就可以考虑抽象化了

5、KISS原则

简单原则

6、POLA原则

最小惊奇原则

144fe83fa972b53414bc20edd4fc0337.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

吕哥架构

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

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

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

打赏作者

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

抵扣说明:

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

余额充值