设计模式系列-设计原则总结

经典的设计原则:SOLID、KISS、YAGNI、DRY、LOD。

下面就分别总结一下这几个原则。

SOLID:

SOLID是由 5 个设计原则组成的,它们分别是:单一职责原则、开闭原则、里式替换原则、接口隔离原则和依赖反转原则,依次对应 SOLID 中的 S、O、L、I、D 这 5 个英文字母。
单一职责原则(SRP):Single Responsibility Principle
一个类或者模块只负责完成一个职责(或者功能)
这个原则,讲究不要设计大而全的类,要设计粒度小,功能单一的类,达到高内聚,低耦合的效果。
在实际开发中,一些关联性不大的类,还是很好判断的,但是有些关联性比较大的类,就不好拿捏了。会感觉到拆开也行,不拆开也可以。举个例子,假如有个用户信息类,UserInfo,里面包含用户姓名,年龄等基本信息,还有电话,邮箱等联系方式,还有省份住址等地址信息。这个类是用一个大类来表示,还是拆分成用户基本信息类,用户联系方式类,用户地址信息类呢。这时候就需要根据当前的业务复用程度来看了,如果只是单纯展示这些信息,可以合并成一个类,如果是还有电商,还有passport等业务,需要复用用户的联系方式和地址信息,就可以拆分成三个类。
在应用单一职责原则时,也需要注意一个度,如果把类的职责设计的过于单一,本该属于一个类的代码拆分成了多个类,也会降低内聚性,使代码变得更难维护。
所以,对于单一职责原则来说,并没有非常明确可以量化的标准,在软件开发中,也没必要一开始就过度设计,可以先写粗粒度的类,随着业务的发展,不断进行持续重构。
开闭原则(OCP):Open Closed Principle
软件实体(模块、类、方法等)应该”对扩展开放、对修改关闭”
详细表述下,就是在添加一个新的功能时,在已有代码基础上扩展代码(新增模块、类、方法等),而非修改已有代码(修改模块、类、方法等)。
需要注意下,这里的软件实体,既可以是模块的维度,也可以是类的维度,还可以是方法的维度。因为在添加新功能时,不可能不修改代码,所以我们要做的就是尽量只改动上层的代码,而且让改动更少,更集中。对于底层的最核心,最复杂的那部分代码满足开闭原则。
开闭原则,针对的是代码的扩展性,在功能开发前,就思考好这段代码未来有可能会有哪些需求变更,设计好代码结构,事先留好扩展点,以便后期有需求变更时,不需要调整代码结构,只是在原有扩展点上进行小的改动即可。
如何做好开闭原则呢,可以在开发时,识别代码中的可变部分和不可变部分,将可变部分封装起来,隔离变化,提供抽象化的不可变接口,给上层系统使用。当具体的实现发生变化的时候,我们只需要基于相同的抽象接口,扩展一个新的实现,替换掉老的实现即可,上游系统的代码几乎不需要修改。
其实,23种经典设计模式,大部分都是为了解决代码的扩展性,都是以开闭原则为指导原则。
同样,开闭原则在使用过程中,也要注意把握一个度,如果业务逻辑简单,只是简单if else就可以实现,那也没必要非得符合开闭原则,后期即使改动业务代码,成本也不高,而且还可以保持很好的可读性。而如果业务复杂,则需要设计的符合开闭原则,虽然可读性会降低,但是后期的扩展维护会变得方便。
里式替换原则(LSP):Liskov Substitution Principle
子类对象(object of subtype/derived class)能够替换程序(program)中父类对象(object of base/parent class)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。
从定义的描述上看,里式替换原则,和多态类似,但它们关注的角度是不一样的,多态是面向对象编程的一种语法,是代码实现的思路。而里式替换是一种设计原则。是用来指导继承关系中子类该如何设计的,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑以及不破坏原有程序的正确性。
子类继承父类的时候,或者实现某个接口的时候,可以改变函数内部的实现逻辑,但是不能改变函数原有的行为约定,这里的行为约定包括:函数声明要实现的功能;对输入、输出、异常的约定;甚至包括注释中所罗列的任何特殊说明。
接口隔离原则(ISP):Interface Segregation Principle
接口调用者不应该被强迫依赖它不需要的接口。
这里的接口,也是个抽象的概念,既可以指一组接口的集合,也可以指单个的API接口或者函数,还可以指OOP中的接口定义。
如果把接口理解为一组接口的集合,可以是某个微服务提供的一组接口,也可以是某个类库提供的一组接口,那么对于接口隔离原则来说,如果部分接口只被部分调用者使用,我们就需要将这部分接口隔离出来,单独给这部分调用者使用,而不强迫其他调用者也依赖这部分不会被用到的接口。
如果把接口理解为单个API接口或者函数,部分调用者只需要函数中的部分功能,那我们就需要把函数拆分成粒度更细的多个函数,让调用者只依赖它需要的那个细粒度函数。
接口隔离原则和单一职责原则有点类似,不过接口隔离原则,更侧重接口的设计,而且接口隔离原则的思想可以作为职责是否单一的判断标准,如果调用者只是用到的接口的一部分功能,或者用到了暴露出来一批接口的一部分接口,那就说明接口设计的职责不够单一。
依赖反转原则(DIP):Dependency Inversion Principle
在说依赖反转之前,先说下控制反转,控制反转,是从程序执行流程角度来讲的,通过反转,把程序的执行流程交给框架来控制,不再是程序员自己写代码控制了,流程的控制权由程序员反转给了框架,控制反转属于一种设计思想。
依赖注入,简单来说,就是不通过new()的方式在类内部创建所依赖的对象,而是通过setter,或者构造函数,或者注入等方式,在外部将依赖的类创建好,再传递进来使用,依赖注入是一种编码技巧。在实际开发中,我们也会使用Spring等类似的框架,把依赖注入的工作交给框架来实现,这样我们就可以更专注于业务。
依赖反转原则,就是说高层模块不能依赖低层模块,高层模块和底层模块之间应该通过抽象来互相依赖,除此之外,抽象不依赖具体的实现细节,而具体的实现细节依赖抽象。依赖反转和控制反转有点类似,主要用来指导框架层面的设计。

KISS:

KISS原则:Keep It Simple and Stupid
KISS原则意思是要尽量保持简单,这里的简单,并不仅仅指的代码行数少。而是要综合来判断,一方面,不要使用一些不常见或者难理解的技术。另一方面,也不要过度的设计,过度的优化,而损失的代码的可读性。

YAGNI:

YAGNI 原则:You Ain’t Gonna Need It
YAGNI的意思翻译为:你不会需要它。
意思就是不要过度设计,如果这个功能当前用不到,就不要去编码。但是还需要兼顾程序的扩展性,提前预留好扩展点。

DRY:

DRY 原则:Don’t Repeat Yourself
DRY原则,意思是不要写重复的代码。但是这里的重复,并不是指代码的重复。有时候,重复的代码是符合DRY原则的,而有时候不重复的代码确是不符合DRY原则的。
比如,有时候一些校验的方法,虽然代码的很多地方是重复的,但是这些代码是用来校验不同的业务,是符合DRY原则的,如果为了减少重复的代码,可以考虑把一些重复的代码进行更进一步的抽象,抽象出多个细粒度的校验方法。
再比如,项目中有两个判断ip是否合法的方法,但是具体的实现方式是不一样的,代码没有任何重复,但是这两个方法是不符合DRY原则的,因为他们的语义和功能是重复的,会给后面使用的人带来理解的难度,并且增加维护的成本。
除此之外,同一个方法中,有一些共同逻辑执行了多次,也不符合DRY原则,比如执行某个业务时,需要先校验,校验通过后,再执行后面的逻辑。而校验需要查询某些信息,执行后面逻辑也需要查询同样的信息,就会导致信息查询多次。
总结一下,就是实现逻辑重复,但功能语义不重复的代码,并不违反 DRY 原则。实现逻辑不重复,但功能语义重复的代码,也算是违反 DRY 原则。除此之外,代码执行重复也算是违反 DRY 原则。

LOD:

LOD原则:Law of Demeter
迪米特法则,定义如下:每个模块(unit)只应该了解那些与它关系密切的模块的有限知识。或者说,每个模块只和自己的朋友“说话”,不和陌生人“说话”。
换句话说,就是我们在设计类的时候,如果两个类没有依赖关系,就不要设计的有依赖,就算是两个类之家有依赖关系,也要只依赖必要的接口。
LOD原则能够让我们设计出高内聚,低耦合的代码。高内聚关注的是类本身的设计,低耦合关注的是类之间的依赖关系。功能相近的功能放到一个类中,不相近的就不放到同一个类中。因为功能相近的代码有可能同时修改,放到同一个类中,就可以使得修改比较集中,减少改动的影响面,容易维护。
所以,其实这些原则,很多都是从不同的角度,来指导高内聚低耦合的,LOD是类之间的依赖关系角度,单一职责原则是从类自身提供接口的角度,针对接口而非实现编程则是从调用者的角度。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值