对面向对象设计原则SOLID的一些理解

高内聚、低耦合

我们常说写代码要注意高内聚、低耦合,那么什么是高内聚、低耦合呢?

高内聚指一个模块只负责一项任务,即单一责任原则。模块的内聚反映模块内部联系的紧密程度。一个模块只需做好一件事件,不要过分关心其他任务。高内聚性的好处是可以提高程序的可靠性。

低耦是指各模块间相互联系紧密程度的一种度量。模块之间联系越紧密,其耦合性就越强,模块的独立性则越差。所以我们要尽量保证低耦合度

SOLID

开放封闭原则:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。

单一职责原则:一个类、接口或方法只负责一个职责,降低代码复杂度以及变更引起的风险。

依赖倒置原则:针对接口编程,依赖于抽象类或接口而不依赖于具体实现类。

接口隔离原则:将不同功能定义在不同接口中实现接口隔离。

里氏替换原则:任何基类可以出现的地方,子类一定可以出现。

迪米特原则:每个模块对其他模块都要尽可能少地了解和依赖,降低代码耦合度。

补充:
合成复用原则:尽量使用组合(has-a)/聚合(contains-a)而不是继承(is-a)达到软件复用的目的。

S单一职责原则

1. 如何理解单一职责原则?

单一职责原则就是要保证高内聚,一个类尽量只负责一个功能。不要设计大而全的类,尽量设计粒度小、功能单一的类

2. 如何判断类的职责是否足够单一?

不同的应用场景、不同阶段的需求背景、不同的业务层面,对同一个类的职责是否单一,可能会有不同的判定结果。
实际上,一些侧面的判断指标更具有指导意义和可执行性,
比如,出现下面这些情况就有可能说明这类的设计不满足单一职责原则

  • 类中的代码行数、函数或者属性过多;
  • 类依赖的其他类过多
  • 私有方法过多;
  • 比较难给类起一个合适的名字;
  • 类中大量的方法都是集中操作类中的某几个属性。

3. 类的职责是否设计得越单一越好?

单一职责原则是为了提高类的内聚性,减少了代码的耦合性。
但是,如果拆分得过细,实际上会适得其反,反倒会降低内聚性,也会影响代码的可维护性。

4.学习心得

通过单一职责原则,我们知道在开发过程中,即要保证高内聚、低耦合,也不能拆分的过细。
所以实际开发中,建议可以先写一个粗粒度的类,满足业务需求。随着业务的发展,如果粗粒度的类越来越庞大,代码越来越多,这个时候,我们就可以将这个粗粒度的类,拆分成几个更细粒度的类。这就是所谓的持续重构

O开闭原则

定义:
对扩展开放,对修改关闭

对扩展开放是为了应对需求变化,
对修改关闭是为了保证已有代码的稳定性;

1. 如何理解“对扩展开放、对修改关闭”?

添加一个新的功能,应该是通过在已有代码基础上扩展代码,而非修改已有代码的方式来完成。关于定义,我们有两点要注意。

  • 第一点是,开闭原则并不是说完全杜绝修改,而是以最小的修改代码的代价来完成新功能的开发。
  • 第二点是,同样的代码改动,在粗代码粒度下,可能被认定为“修改”;在细代码粒度下,可能又被认定为“扩展”。

2. 如何做到“对扩展开放、修改关闭”?

我们要时刻具备扩展意识、抽象意识、封装意识。在写代码的时候,我们要多花点时间思考一下,这段代码未来可能有哪些需求变更,如何设计代码结构,事先留好扩展点,以便在未来需求变更的时候,在不改动代码整体结构、做到最小代码改动的情况下,将新的代码灵活地插入到扩展点上。

很多设计原则、设计思想、设计模式,都是以提高代码的扩展性为最终目的的。特别是 23种经典设计模式,大部分都是为了解决代码的扩展性问题而总结出来的,都是以开闭原则为指导原则的

最常用来提高代码扩展性的方法有:多态、依赖注入、基于接口而非实现编程,以及大部分的设计模式(比如,装饰、策略、模板、职责链、状态)。

L里氏替换原则

定义:
父类对象出现的地方可以用子类对象替换,并且保证原来程序的逻辑行为不变及正确性不被破坏。

乍一看是不是觉得里氏替换原则和多态很像,但它们实际是有本质区别的。

里氏替换原则和多态都需要继承,关键就在于继承之后有哪些区别:

  1. 里氏替换原则中,继承只是为了实现代码重用,父类方法应该保持不变,不能被子类重写。子类想要扩展新的功能只能通过添加新的方法来实现。这是和多态最大的区别,里氏替换原则中子类不重写父类的方法,而多态是子类必须重写父类的方法
    所以里氏替换原则中,父类和子类都可以实例化,而子类继承的方法和父类是一样的,父类调用方法的地方,子类也可以调用同一个继承得来的,逻辑和父类一致的方法,这时用子类对象将父类对象替换掉时,当然逻辑一致,相安无事。

  2. 而多态中,子类需要覆盖并重新定义父类的方法,此时如果为了符合LSP,我们应该将父类定义为抽象类,并定义抽象方法,让子类重新定义这些方法,当父类是抽象类时,父类就是不能实例化,所以也不存在可实例化的父类对象在程序里。也就不存在子类替换父类实例(根本不存在父类实例了)时逻辑不一致的可能。

所以说,典型的不符合LSP的情况是:
父类和子类都是可实例化的非抽象类,且父类的方法被子类重新定义
父子类对同一方法的具体实现不一样了,当然就不能用子类去替换父类了,
造成父类和子类间的强耦合,也就是实际上并不相关的属性和方法牵强附会在一起,不利于程序扩展和维护。

那么如何确保符合LSP?
一句话:就是不要从可实例化的父类中继承,而是要使用基于抽象类和接口的继承。

I接口隔离原则

理解“接口隔离原则”的重点是理解其中的“接口”二字。这里有三种不同的理解。

  • 如果把“接口”理解为一组接口集合,可以是某个微服务的接口,也可以是某个类库的接口等。如果部分接口只被部分调用者使用,我们就需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口。

  • 如果把“接口”理解为单个 API 接口或函数,部分调用者只需要函数中的部分功能,那我们就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数。

  • 如果把“接口”理解为面向对象编程语言中的接口语法。那接口的设计要尽量单一,不要让接口的实现类和调用者,依赖不需要的接口函数。

接口隔离原则 vs 单一职责原则

单一职责原则针对的是模块、类、接口的设计。
接口隔离原则相对于单一职责原则,一方面更侧重于接口的设计,另一方面它的思考角度也是不同的。接口隔离原则提供了一种判断接口的职责是否单一的标准:通过调用者如何使用接口来间接地判定。如果调用者只使用部分接口或接口的部分功能,那接口的设计就不够职责单一。

D依赖反转原则

定义:
高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口;
抽象接口不应该依赖于具体实现。而具体实现则应该依赖于抽象接口。

定义未免过于抽象,怎么理解呢?
比如,货币出现之前,商品交易是物物交换。如果你要买一台手机,卖手机的人让你拿一头猪来换,但是你手里没有猪,这时,你就要去找一个卖猪的老板,但是他要你拿羊来跟他换,你也没有羊,继续去找卖羊的人…

你看,这一连串的对象依赖,造成了严重的耦合灾难。解决这一问题的最好办法就是,买卖双方都依赖一个抽象——货币,通过货币来进行交换,这样一来耦合度就大为降低了。

参考文章

极客时间
参考文章

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值