Java设计原则核心要点解析:开闭、里氏、迪米特与DIP原则详解

做Java开发的朋友,肯定都听过面向对象设计的五大原则,这些原则是写好可维护、可扩展代码的核心准则。很多人刚接触的时候,会觉得这些原则说起来很抽象,不知道实际写代码的时候怎么用。今天我们就挑几个最核心的,好好理一理它们到底讲了什么,实际用的时候要注意什么。

第一个要说的就是开闭原则,这应该是所有设计原则里最基础的一个了。开闭原则的定义其实很简单,就是对扩展开放,对修改关闭。

举个例子你就能懂了,你做了一个电商平台的折扣计算功能,一开始只给普通会员打9折。后来业务变了,要给高级会员打8折,钻石会员打7折。如果不遵守开闭原则,你肯定会直接打开原来写折扣计算的那个类,改掉里面的if-else逻辑,加两个新的分支进去。改原来的代码看起来很快,可问题也不少,原来的9折逻辑已经跑了很久了,你改代码的时候万一不小心动到原来的逻辑,线上就出bug了。而且每次加会员等级都改同一个类,这个类会越来越臃肿,最后谁都不敢改。

如果按照开闭原则来做,你会先抽一个折扣计算的抽象接口,每个会员等级的计算都做成单独的实现类。以后加新的会员等级,直接写新的实现类就好了,原来的代码碰都不用碰。这样原来逻辑出问题的概率就降下来了,代码也更清晰。说白了,开闭原则就是让我们尽量用加代码代替改代码,减少对现有稳定逻辑的影响。

接下来讲里氏替换原则。这个原则的名字听起来有点奇怪,其实核心就是一句话:子类可以替换掉父类,程序的逻辑不会出问题。

我们都知道面向对象里继承的概念,子类继承父类之后,可以拿到父类的所有非私有属性和方法。但很多人继承用错了地方,比如大家最熟悉的一个例子,正方形是不是长方形?从数学定义来说,正方形是特殊的长方形,长和宽相等。那按照这个逻辑,是不是可以让正方形继承长方形?

如果真这么写,就出问题了。长方形有setWidth和setHeight两个方法,分别设置长和宽。正方形长和宽必须一样,所以子类里重写这两个方法的时候,会同时改长和宽。这时候如果写了一个计算长方形面积的方法,比如给长方形设置长10宽20,预期面积是200。如果把正方形传进去,你设置长10之后,宽也变成10,再设置宽20,长又变成20,最后面积是400,和预期完全不一样。这就违反了里氏替换原则,子类不能替换父类,原来跑着没问题的逻辑,换了子类就出错了。

所以说,继承不是只要逻辑上有关系就能用,要保证子类能完全支撑父类的所有约定,不能改了父类原本的行为逻辑。如果做不到,那就别用继承,换组合来实现复用更稳妥。

然后我们说迪米特法则,这个也叫最少知识原则。说直白点,就是一个类应该对其他类知道得越少越好,只和你必须打交道的朋友说话,不和陌生人说话。

我举个常见的场景,你做一个外卖下单的功能,有几个类:订单类、商家类、厨房类、做菜步骤类。现在订单类要触发做菜,如果你写代码的时候,订单类先拿到商家实例,再从商家实例拿到厨房实例,再从厨房实例拿到做菜步骤实例,最后调用做菜方法。这么写下来,订单类居然要知道厨房、知道做菜步骤,它本来只需要管下单的事情,现在跟完全不相关的类都耦合上了。哪天做菜步骤改了个名字,或者厨房类换了结构,订单类也要跟着改,维护起来特别麻烦。

按照迪米特法则改的话,订单只需要调用商家的开始接单方法就好了,商家自己去调用厨房做菜,厨房自己去处理做菜步骤,订单完全不需要知道后面这几步都是谁在干活。这样每个类只跟自己直接相关的类交互,不用管更深层的依赖,耦合度就降下来了,改代码的时候也不会牵一发动全身。

有人可能会说,那按照迪米特法则,是不是会搞出来很多很多中间类?确实会多几个转发的类,但这点复杂度换来的低耦合,长期来看绝对是划算的,不然大项目改一处崩一片,谁都顶不住。

最后说依赖倒置原则,简称DIP。很多人搞不清这个原则到底说的是什么,它的核心其实就是两点:高层模块不应该依赖低层模块,两者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。

还是用例子说,你做了一个后台管理系统,一开始数据都存在MySQL里,所以业务逻辑层(就是我们说的高层模块)直接依赖了MySQL操作的具体类(低层模块)。后来业务做大了,数据要换MongoDB存,你就得改业务逻辑层的代码,把原来调用MySQL操作的地方全换成MongoDB的操作,改起来要命。

这就是违反了依赖倒置,高层直接依赖了低层的细节,换细节就得改高层。如果用依赖倒置来做,我们先抽一个数据操作的抽象接口,不管存MySQL还是Mongo,都得实现这个接口定义的方法。业务逻辑层只依赖这个抽象接口,根本不管你数据到底存在哪。换数据库的时候,只需要写个新的实现类,业务逻辑层一行代码都不用改,直接注入新实现就能用了。

简单说,依赖倒置就是让我们面向接口编程,不要面向具体实现编程。抽象稳定,细节容易变,依赖稳定的抽象,比依赖多变的细节靠谱多了。

其实这几个原则放在一起看,目标都是一样的,就是降低代码的耦合度,提升代码的可扩展性和可维护性。你不用刚写代码就硬套所有原则,上来就把接口抽得满天飞,反而把简单问题复杂化了。一般来说,代码第一次需要改需求扩展的时候,你顺着这些原则的思路调整就好,慢慢就能写出结构更清晰的代码了。

很多人觉得设计原则都是八股文,面试用用就行,实际写代码不用管。但真做过几年大型项目的人就知道,项目能越改越轻松,还是越改越乱,很大程度上就是看有没有遵守这些最基本的设计原则。这些原则不是束缚,是帮我们少踩坑的经验总结,多想想为什么要这么做,用的时候自然就顺手了。

Java设计原则,开闭原则,里氏替换原则,迪米特法则,DIP原则,依赖倒置原则,面向对象设计,设计原则解析,Java开发,代码可维护性

[Q]:什么是Java设计中的开闭原则?
[A]:开闭原则的核心是对扩展开放、对修改关闭,要求我们尽量通过新增代码实现功能变更,而非修改原有已稳定运行的代码,避免修改原有代码引发bug,提升代码可扩展性。
[Q]:里氏替换原则的核心要求是什么?
[A]:里氏替换原则要求子类可以完全替换父类,替换后程序原有逻辑不会出错,子类不能修改父类原本约定的行为逻辑,避免继承使用不当引发的逻辑错误。
[Q]:迪米特法则又被叫做什么?
[A]:迪米特法则也叫最少知识原则,核心要求是一个类只和自己必须交互的类产生关联,尽量减少对其他无关类的依赖,降低代码耦合度。
[Q]:按照迪米特法则写代码会有什么问题吗?
[A]:按照迪米特法则开发确实会增加少量用于转发逻辑的中间类,但这点复杂度的增加远低于低耦合带来的维护收益,整体更利于大型项目的长期维护。
[Q]:依赖倒置原则DIP的核心内容是什么?
[A]:依赖倒置原则核心有两点,一是高层模块不依赖低层模块,二者都依赖抽象;二是抽象不依赖细节,细节依赖抽象,本质就是要求面向接口编程,而非面向具体实现编程。
[Q]:正方形继承长方形的写法为什么违反里氏替换原则?
[A]:正方形长和宽必须相等,重写长方形的长、宽设置方法后,会同时修改长和宽,当原有长方形面积计算方法替换为正方形实例后,得到的结果和预期完全不符,无法替换父类,因此违反原则。
[Q]:这些Java设计原则的共同目标是什么?
[A]:这些设计原则的共同目标都是降低代码耦合度,提升代码的可扩展性和可维护性,让项目在迭代过程中不容易变得臃肿混乱,降低长期维护的成本。
[Q]:写代码是不是一开始就要硬套所有设计原则?
[A]:不需要一开始就硬套所有原则,过度设计反而会把简单问题复杂化,一般在第一次需要扩展修改需求时,顺着设计原则的思路调整代码即可,逐步优化代码结构。
share