Back
Featured image of post 设计模式-设计原则

设计模式-设计原则

一、简言

最近在学习设计模式,顺便推荐一本学习设计模式的好书:《head first 设计模式》,这本书用图画和鲜明例子讲解了设计模式。我将把本书中学习到的设计模式进行整理分享给大家。

二、设计模式是什么?

​ 说到设计模式,一听就是很高大上的词,第一反应就是很深奥,完全理解不了这个概念到底是什么意思,下面我先从网上摘录一份定义。

设计模式(Designpattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

反复使用: 这个不用过多解释,设计模式被使用太多了,许多框架的源码当中就出现了很多模式,记忆中比较深刻的有模板模式,代理模式,单例模式,工厂模式等等。

多数人知晓: 这个就不需要过多解释了。

分类编目: 就是说可以找到一些特征去划分这些设计模式,从而进行分类。

代码设计经验: 这句很重要,设计经验的总结,也就是说设计模式,是为了指导设计而从经验中总结出来的套路。

通过设计模式的概念我们可以看出设计模式对于我们代码的编写优化有着至关重要的作用,并且它与面向对象是十分契合的。正如书中所说:”信耶稣的人都要读圣经,而信OO(面向对象)的人都要读《设计模式》"

三、为什么要学设计模式

​ 对于刚入门的程序员,你是否也有这样的困惑?

  • 当按照自己的逻辑写完一个需求后,发现自己的代码写了一大堆并且有很多重复的冗余代码。

  • 当你发现自己的代码写的很冗余的时候想要优化,却不知道如何进行代码的优化。

  • 你是否在代码设计时只考虑了现在,并没有考虑以后有新需求时如何加入当前项目。

我想经过这几点,已经足够让我们去学习设计模式,而我们学习设计模式的最终目的就是:将模式装进脑子里,然后在你的设计和已有的应用中,寻找何处使用它们。

四、设计模式的六大原则

​ 在我们在学习设计模式之前,为了让模式不显得特别刻板化,我们应该先学习一下设计原则。这些原则是指导模式的规则,但是并不是说我们要完全遵循,因为原则是死的而人是活的,有些设计模式中可能也会违背这些原则中的某一/几项。就如同数据库的范式,只是在尽可能的情况下遵守。

1、单一职责原则(Single Responsibility Principle)

There should never be more than one reason for a class to change.

描述的意思是每个类都只负责单一的功能,切不可太多,并且一个类应当尽量的把一个功能做到极致。它让类的职责更单一。这样的话,每个类只需要负责自己的那部分,类的复杂度就会降低。如果职责划分得很清楚,那么代码维护起来也更加容易。试想如果把大量功能都放在一个类中,倘若某些功能需要修改或出现Bug,那么是不是需要修改整个类中大量的代码呢?

2、里氏替换原则(Liskov Substitution Principle)

Functions that use use pointers or references to base classes must be able to use objects of derived classes without knowing it.

它描述的意思是所有引用父类的地方,都可以用它的子类去替换,程序还可以正常运行。 在学习java类的继承时,我们知道继承有一些优点:

  • 子类拥有父类的所有方法和属性,从而可以减少创建类的工作量。
  • 提高了代码的重用性。

  • 提高了代码的扩展性,子类不但拥有了父类的所有功能,还可以添加自己的功能。

但有优点也同样存在缺点:

  • 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法。
  • 降低了代码的灵活性。因为继承时,父类会对子类有一种约束。
  • 增强了耦合性。当需要对父类的代码进行修改时,必须考虑到对子类产生的影响。

而通过里氏替换原则就能够很好的进行扬长避短,里氏替换原则对继承有个四方面的约束:

  • 子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
  • 子类中可以增加自己特有的方法。
  • 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比- 父类方法的输入参数更宽松。(即只能重载不能重写)。
  • 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

3、接口隔离原则(Interface Segregation Principle)

Clients should not be forced to depend upon interfaces that they don`t use.

The dependency of one class to another one should depend on the smallest possible.

通过里氏替换原则我们明白了对于继承的约束,那么接口隔离原则强调的就是 一个接口拥有的行为应该尽可能的小。你可能发现这样一种状况那就是,可能一个类可能只实现了某个接口的几个方法,还有一些方法是空着的,这样做不仅会强制实现的人不得不实现本来不该实现的方法,最严重的是会给使用者造成假象,即这个实现类拥有接口中所有的行为,结果调用方法时却没收获到想要的结果。

通过上面的描述你可能会觉得接口隔离原则和单一职责比较像,接口隔离原则和单一职责都是为了提高类的内聚性、降低它们之间的耦合性,体现了封装的思想,但两者是不同的:

  • 单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。

  • 单一职责原则主要是约束类,它针对的是程序中的实现和细节;接口隔离原则主要约束接口,主要针对抽象和程序整体框架的构建。

4、依赖倒置原则(Dependence Inversion Principle)

High level modules should not depend upon low level modules. Both should depend upon abstractions.

Abstractions should not depend upon details. Details should depend upon abstractions.

高层模块不该依赖于低层模块,二者都应该依赖于抽象,抽象不应该依赖于细节,细节应该依赖于抽象

举个例子:我们有一个水果店,水果店里有各种各样的水果,那就好比水果店是一个高层模块,而水果店里的水果作为底层模块。我们把依赖化成一张图就会有如下。

通过此图我们可以发现上层水果店类依赖了大量的下层水果类,这造成了程序十分耦合且依赖性很强。现在“倒置”你的想法……别从上层模块水果店开始思考,而是从下层模块具体水果开始,然后想想看能抽象化些什么。我们可以将各种各样的水果都整合成一个共享的水果接口,现在重新设计这个水果店如下图:

我们可以观察到依赖确实倒置了,并且此例子也正好解释了高层模块不该依赖于低层模块,二者都应该依赖于抽象

5、迪米特法则(Law of Demeter)

Talk only to your immediate friends and not to strangers

也称最小知道原则,如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦合度,提高模块的相对独立性。。例如:服务中的网关,前端只需要请求到网关无需直接请求某个具体的服务。

迪米特法则的主要优势是:

  • 降低了类之间的耦合度,提高了模块的相对独立性。
  • 由于亲合度降低,从而提高了类的可复用率和系统的扩展性。

6、开闭原则(Open Closed Principle)

Software entities like classes, modules and functions should be open for extension but closed for modification

开闭原则的意思是 对修改关闭,对扩展开放。这个原则是所有原则中系统设计的理想境界,比如强大的Spring就很好的利用了这一点。用抽象构建框架,用细节实现扩展。这也完全符合面向对象的含义,这个原则也更像是前面五个设计原则的总则一样。