【转】设计模式六大原则——SOLID

SOLID设计模式的六大原则有:

  1. Single Responsibility Principle:单一职责原则
  2. Open Closed Principle:开闭原则
  3. Liskov Substitution Principle:里氏替换原则
  4. Law of Demeter:迪米特法则
  5. Interface Segregation Principle:接口隔离原则
  6. Dependence Inversion Principle:依赖倒置原则

把这六个原则的首字母联合起来( L 算做一个)就是 SOLID (solid,稳定的),其代表的含义就是这六个原则结合使用的好处:建立稳定、灵活、健壮的设计

下面我们来分别看一下这六大设计原则。

单一职责原则(Single Responsibility Principle)

单一职责原则简称 SRP ,顾名思义,就是一个类只负责一个职责。它的定义也很简单:

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


这句话很好理解,就是说,不能有多个导致类变更的原因。

那这个原则有什么用呢,它让类的职责更单一。这样的话,每个类只需要负责自己的那部分,类的复杂度就会降低。如果职责划分得很清楚,那么代码维护起来也更加容易。试想如果所有的功能都放在了一个类中,那么这个类就会变得非常臃肿,而且一旦出现bug,要在所有代码中去寻找;更改某一个地方,可能要改变整个代码的结构,想想都非常可怕。当然一般时候,没有人会去这么写的。

当然,这个原则不仅仅适用于类,对于接口和方法也适用,即一个接口/方法,只负责一件事,这样的话,接口就会变得简单,方法中的代码也会更少,易读,便于维护。

事实上,由于一些其他的因素影响,类的单一职责在项目中是很难保证的。通常,接口和方法的单一职责更容易实现。

单一原则的好处:

代码的粒度降低了,类的复杂度降低了。
可读性提高了,每个类的职责都很明确,可读性自然更好。
可维护性提高了,可读性提高了,一旦出现 bug ,自然更容易找到他问题所在。
改动代码所消耗的资源降低了,更改的风险也降低了。

里氏替换原则(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. 
翻译过来就是,所有引用基类的地方必须能透明地使用其子类的对象。

里氏替换原则的意思是,所有基类在的地方,都可以换成子类,程序还可以正常运行。这个原则是与面向对象语言的 继承 特性密切相关的。

这是为什么呢?由于面向对象语言的继承特性,子类拥有父类的所有方法,因此,将基类替换成具体的子类,子类也可以调用父类中的方法(其实是它自己的方法,继承于父类),但是如果要保证完全可以调用,光名称相同不行,还需要满足下面两个条件:

子类中的方法的前置条件必须与超类中被覆写的方法的前置条件相同或更宽松。
子类中的方法的后置条件必须与超类中被覆写的方法的后置条件相同或更严格。
这样的话,调用就没有问题了。否则,我在父类中传入一个 List 类型的参数,子类中重写的方法参数却变为 ArrayList ,那客户端使用的时候传入一个 LinkedList 类型的参数,使用父类的时候程序正常运行,但根据 LSP 原则,替换成子类后程序就会出现问题。同理,后置条件也是如此。

是不是很好理解?

但是这个原则并不是这么简单的,语法上是肯定不能有任何问题。但是,同时,功能上也要和替换前相同。

那么这就有些困难了,因为子类重写父类中的方法天经地义,子类中的方法不同于父类中的方法也是很正常的,但是,如果这么随便重写父类中的方法的话,那么肯定会违背 LSP 原则,可以看下面的例子:

首先有一个父类,水果类:

public class Fruit {
void introduce() {
System.out.println("我是水果父类...");
}
}
其次,它有一个子类,苹果类:

public class Apple extends Fruit {
@Override
void introduce() {
System.out.println("我是水果子类——苹果");
}
}


客户端代码如下:

public static void main(String[] args) {
Fruit fruit = new Fruit();
HashMap map = new HashMap<>();
fruit.introduce();
}

运行结果:

我是水果父类...

那么,如果按照 LSP 原则,所有父类出现的地方都能换成其子类,代码如下:

public static void main(String[] args) {
Apple fruit = new Apple();
HashMap map = new HashMap<>();
fruit.introduce();
}

那么运行结果就会变成:

我是水果子类——苹果

与原来的输出不同,程序的功能被改变了,违背了 LSP 原则。

因此,可以看到, LSP 原则最重要的一点就是:避免子类重写父类中已经实现的方法。这就是 LSP 原则的本质。这里由于 Fruit 父类已经实现了 introduce 方法,因此子类应该避免再对其进行重写,如果需要增加个性化,就应该对父类进行扩展,而不是重写,否则也会违背开闭原则。

一般来讲,程序中父类大多是抽象类,因为父类只是一个框架,具体功能还需要子类来实现。因此很少直接去 new 一个父类。而如果出现这种情况,那么就说明父类中实现的代码已经很好了,子类只需要对其进行扩展就会,尽量避免对其已经实现的方法再去重写。

依赖倒置原则(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.

翻译一下,就是下面三句话:

高层模块不应该依赖底层模块,两者都应该依赖其抽象。
抽象不应该依赖细节。
细节应该依赖抽象。
在Java语言中的表现就是:

模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口或抽象类产生的。
接口或抽象类不依赖于实现类。
实现类依赖于接口或抽象类。
简而言之,我们要尽可能使用接口或抽象类。也就是“面向接口编程” 或者说 “面向抽象编程” ,也就是说程序中要尽可能使用抽象类或是接口。

可能现在还没有使用抽象的习惯,可以看一个例子:比如我们要写一个人吃苹果的程序,首先创建一个苹果类:

public class Apple {
public void eaten() {
System.out.println("正在吃苹果...");
}
}
然后写人的类:

public class Person {
void eat(Apple apple) {
apple.eaten();

}
这样,在客户端中我们就可以这样调用:

Person xiaoMing = new Person();
Apple apple = new Apple();
xiaoMing.eat(apple);
但是这样就有一个问题,如果我不仅仅想吃苹果,还想吃橘子怎么办,在 Person 类中再加一个函数处理橘子?那么吃别的水果呢??总不能程序中每增加一种水果就要在其中增加一个函数吧。

这时候就需要接口了(抽象类也是可以的)

程序就可以这样更改,增加一个水果接口:

public interface Fruit {
void eaten(); 
}
让苹果类实现该接口:

public class Apple implements Fruit {
@Override
public void eaten() {
System.out.println("正在吃苹果...");
}
}
然后将Person类中的函数的参数稍作修改:

public class Person {
void eat(Fruit fruit) {
fruit.eaten();
}
}
这样,客户端代码也稍作修改:

Person xiaoMing = new Person();
Fruit apple = new Apple();
xiaoMing.eat(apple);
这样改完之后,如果再增加一种新的水果,就不需要改变 Person 类了,方便吧。

那么再回来说我们的依赖倒置原则,依赖就是类与类之间的依赖,主要是函数传参,上面的例子已经很明白地介绍了,参数要尽可能使用抽象类或接口,这就是“高层模块不应该依赖底层模块,两者都应该依赖其抽象” 的解释。那么如果要实现这个,就要求每个实现类都应该尽可能从抽象中派生,这就是上面的 “细节应该依赖抽象”。

简而言之,该原则主要有下面几点要求:

每个类都尽量要有接口或抽象类,或者两者都有
变量的表面类型尽量是接口或者抽象类(比如程序中的 Fruit apple = new Apple() Fruit 是表面类型, Apple 是实际类型)
任何类都不应该从具体类中派生

接口隔离原则(Interface Segregation Principle)

首先声明,该原则中的接口,是一个泛泛而言的接口,不仅仅指Java中的接口,还包括其中的抽象类。

首先,给出该原则的定义,该原则有两个定义:

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.

类间的依赖关系应该建立在最小的接口上。

这是什么意思呢,这是让我们把接口进行细分。举个例子,如果一个类实现一个接口,但这个接口中有它不需要的方法,那么就需要把这个接口拆分,把它需要的方法提取出来,组成一个新的接口让这个类去实现,这就是接口隔离原则。简而言之,就是说,接口中的所有方法对其实现的子类都是有用的。否则,就将接口继续细分。

看起来,该原则与单一职责原则很相像。确实很像,二者都是强调要将接口进行细分,只不过分的方式不同。单一职责原则是按照 职责 进行划分接口的;而接口隔离原则则是按照实现类对方法的使用来划分的。可以说,接口隔离原则更细一些。

要想完美地实现该原则,基本上就需要每个实现类都有一个专用的接口。但实际开发中,这样显然是不可能的,而且,这样很容易违背单一职责原则(可能出现同一个职责分成了好几个接口的情况),因此我们能做的就是尽量细分。

该原则主要强调两点:

接口尽量小。

就像前面说的那样,接口中只有实现类中有用的方法。

接口要高内聚

就是说,在接口内部实现的方法,不管怎么改,都不会影响到接口外的其他接口或是实现类,只能影响它自己。

迪米特法则(Law of Demeter)
迪米特法则也叫最少知道原则(Least Knowledge Principle, LKP ),虽然名称不同,但都是同一个意思:一个对象应该对其他对象有最少的了解。

该原则也很好理解,我们在写一个类的时候,应该尽可能的少暴露自己的接口。什么意思呢?就是说,在写类的时候,能不 public 就不 public ,所有暴露的属性或是接口,都是不得不暴露的,这样的话,就能保证其他类对这个类有最少的了解了。

这个原则也没什么需要多讲的,调用者只需要知道被调用者公开的方法就好了,至于它内部是怎么实现的或是有其他别的方法,调用者并不关心,调用者只关心它需要用的。反而,如果被调用者暴露太多不需要暴露的属性或方法,那么就可能导致调用者滥用其中的方法,或是引起一些其他不必要的麻烦。

开闭原则(Open Closed Principle)

开闭原则所有设计模式原则中,最基础的那个原则。首先,还是先来看一下它的定义:

Software entities like classes, modules and functions should be open for extension but closed for modifications.
翻译过来就是:一个软件实体,如类、模块和函数应该对扩展开放,对修改关闭。

开闭原则之前也提到过,在 LSP 中,我们说,要避免子类重写父类中已经实现的方法。这个时候,继承父类就是对其进行扩展,但没有进行修改。这就是开闭原则一个很好的体现。

那是不是开闭原则与 LSP 原则混淆了呢?并不是, LSP 原则强调的是基类与子类的关系,只是其中的一种实现方式用到了开闭原则而已。

那么开闭原则具体是什么呢?可以说,开闭原则贯穿于以上五个设计模式原则。开闭原则中的对扩展开放,就是说,如果在项目中添加一个功能的时候,可以直接对代码进行扩展;如果要修改某一部分的功能时,我们应该做的是,尽量少做修改(完全不修改是不可能的),但是修改的时候,要保留原来的功能,只是在上面扩展出新的功能,就像版本更新一样,更新后,依然支持旧版本。

开闭原则是一个特别重要的原则,无论是在设计模式中还是在其他领域中,这都是一个非常基础的设计理念。

总结

总而言之,这六个设计模式原则是以后学习设计模式的基础,它们的共同目的就是 SOLID ——建立稳定、灵活、健壮的设计。

但是原则归原则,有时候由于种种原因,这些条条框框是不得不去打破的,一味地遵循它是不会有好果子吃的(就像接口隔离原则,不可能创建那么多的接口)。因此我们应该正确使用这些原则,主要目的还是为了我们软件的稳定性、灵活性、健壮性和可维护性。

注:本文所有原则定义皆出自《设计模式之禅》。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/439004.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CCNP-第十九篇-ISIS(二)

CCNP-第十九篇-ISIS(二) 首先来个对比的通信机制 首先呢,工作环境中,他没OSPF那么复杂哈,然后 底层启了ISIS,通了就不用管它了 实验环境 注意,上图是错误示范哦! 为什么呢? 为啥呢,前面不是说过ISIS是以路由器为单位的吗? 是的没错但是 L1和L2是无法直接连接到一起去的 …

XCIE-HUAWEI-Cisco-思科-华为-华三堆叠(理论+实操)

XCIE-HUAWEI-Cisco-思科-华为-华三堆叠(理论实操) 首先呢 华为:框式机的堆叠技术称为CSS&#xff0c;盒式机称为iStack。 思科:VSS(对标istck)Flexstack(对标CSS) 锐捷:VSU H3C:不管盒式还是框式&#xff0c;统称为irf 这个盒式设备和框式设备咋理解呢? 华为:https://forum.…

云计算-Linux-云计算是啥.什么是Linux-小白

云计算-Linux-云计算是啥.什么是Linux-小白 20年前说到这个词,你这骗人的吧? 那就是2000年的那场IT峰会了 谁呢 深圳腾 搜索宏 杭州马 到后来,马云回到了阿里巴巴 这个云厂商的第一呢,就是亚马逊的了. 就像数通里面的思科那种感觉了 在这里呢,除了华为和百度和腾讯云用的多…

ABP入门系列(1)——通过模板创建MAP版本项目

一、从官网创建模板项目 进入官网下载模板项目 依次按下图选择&#xff1a; 输入验证码开始下载 下载提示&#xff1a; 二、启动项目 使用VS2015打开项目&#xff0c;还原Nuget包&#xff1a; 设置以Web结尾的项目&#xff0c;设置为启动项目&#xff1a; …

云计算-Linux-VMware安装Centos

云计算-Linux-VMware安装Centos 首先这个,介绍一下哈,VMware是常见的虚拟机的承载平台 对应的还有这个ESXI,vmware vsphere等 对于我们个人用户来说,都是这个VMware workstations比较多的 VM安装包百度网盘如下(永久) 链接&#xff1a;https://pan.baidu.com/s/1XquCdbMsX0gjJ…

ABP入门系列(2)——领域层创建实体

这一节我们主要和领域层打交道。首先我们要对ABP的体系结构以及从模板创建的解决方案进行一一对应。网上有代码生成器去简化我们这一步的任务&#xff0c;但是不建议初学者去使用。 一、首先来看看ABP体系结构 ABP体系结构 领域层就是业务层&#xff0c;是一个项目的核心&…

云计算-Linux-远程连接,Linux岗位介绍

云计算-Linux-远程连接,Linux岗位介绍 使用Xshell,CRT等工具,可以连接到linux主机里面,SSH是默认打开的, 然后就不需要这个在vm里面的那个窗口操作,可以在这个软件里面直接操作 以这个xshell为例子 直接ssh ip地址即可 这个呢机器装完了,就讲讲这个linux的岗位介绍吧 这个的…

软件架构设计的6大原则

1. 单一职责原则&#xff08;Single Responsibility Principle - SRP&#xff09; 原文&#xff1a;There should never be more than one reason for a class to change. 译文&#xff1a;永远不应该有多于一个原因来改变某个类。 理解&#xff1a;对于一个类而言&#xff0c;…

云计算-Linux系统基本概念,命令终端字段含义介绍,命令行格式介绍

云计算-Linux系统基本概念,命令终端字段含义介绍,命令行格式介绍 Linux系统的概念 1.多用户系统,允许多个用户登录系统,使用系统的资源(跟windows不一样,比如RDP远程连接,他只能一个用户在,其他用户上来就把他挤掉了) 最高是65535,因为端口一共就这么多,不过实际上不会这样干…

三层架构与MVC的区别

我们平时总是将混为一谈&#xff0c;殊不知它俩并不是一个概念。下面我来为大家揭晓我所知道的一些真相。 首先&#xff0c;它俩根本不是一个概念。 三层架构是一个分层式的软件体系架构设计&#xff0c;它可适用于任何一个项目。 MVC是一个设计模式&#xff0c;它是根据项目的…

云计算-Linux文件类型介绍,归属关系,基本权限介绍

云计算-Linux文件类型介绍,归属关系,基本权限介绍 这个点我是真滴不困好吧,学习吧那就~ 隐藏文件 上一章没有讲,这个呢,ls -a的命令他是可以显示隐藏的文件的,那问题来了,在windows里面我们可以看到的隐藏文件是暗色的,那么Linux里面的是? 是这样的哦 在Linux里面,文件的前面…

为什么DDD是设计微服务的最佳实践

在本人的前一篇文章《不要把微服务做成小单体》中&#xff0c;现在很多的微服务开发团队在设计和实现微服务的时候觉得只要把原来的单体拆小&#xff0c;就是微服务了。但是这不一定是正确的微服务&#xff0c;可能只是一个拆小的小单体。这篇文章让我们从这个话题继续&#xf…

浅析DDD(领域驱动设计)

最近在做一些微服务相关的设计&#xff0c;内容包括服务的划分&#xff0c;Restful API的设计等。其中比较棘手的就是Service的职责划分&#xff1a;如何抽象具有统一业务范畴的Model&#xff0c;使其模块化&#xff0c;又如何高度提炼并组合多模块&#xff0c;使得业务可独立服…

Microsoft Azure 中的 SharePoint Server 2013 灾难恢复

摘要&#xff1a; 使用 Azure&#xff0c;你可以为内部部署 SharePoint 服务器场创建灾难恢复环境。本文介绍如何设计和实施此解决方案。 观看 SharePoint Server 2013 灾难恢复概述视频 当灾难袭击你的 SharePoint 内部部署环境时&#xff0c;头等大事是迅速使系统恢复运行。…

ELK Stack 与 Elastic Stack 的异同点

在很多场合&#xff0c;都可以看到 ELK Stack 或者是 Elastic Stack 的介绍&#xff0c;大多数人都会产生疑问&#xff0c;这两者到底有什么区别&#xff1f;本文将介绍 ELK Stack 与 Elastic Stack 的异同点。 什么是 ELK Stack 那么&#xff0c;什么是 ELK &#xff1f; “…

详解日志采集工具--Logstash、Filebeat、Fluentd、Logagent对比

概述 常见的日志采集工具有Logstash、Filebeat、Fluentd、Logagent、rsyslog等等&#xff0c;那么他们之间有什么区别呢?什么情况下我们应该用哪一种工具? Logstash Logstash是一个开源数据收集引擎&#xff0c;具有实时管道功能。Logstash可以动态地将来自不同数据源的数据…

云计算-Linux-计算机硬件组成介绍-Linux系统目录介绍

云计算-Linux-计算机硬件组成介绍-Linux系统目录介绍 计算机硬件组成部分 这个感觉就真滴教超级小白了,但是还是讲讲吧 虽然我也感觉在这个地方讲怪怪的 输出设备:鼠标,键盘,触控板 主机设备:主机,CPU,内存,网卡,声卡,显卡 输出设备:屏幕,耳机,打印机 外部存储设备:硬盘,u盘…

rsyslog syslog详解

前言&#xff1a; rsyslog 是一个 syslogd 的多线程增强版。syslog是Linux系统默认的日志守护进程。默认的syslog配置文件是/etc/syslog.conf文件。程序&#xff0c;守护进程和内核提供了访问系统的日志信息。因此&#xff0c;任何希望生成日志信息的程序都可以向 syslog 接口…

第一节:框架前期准备篇之Log4Net日志详解

一. Log4Net简介 Log4net是从Java中的Log4j迁移过来的一个.Net版的开源日志框架&#xff0c;它的功能很强大&#xff0c;可以将日志分为不同的等级&#xff0c;以不同的格式输出到不同的存储介质中&#xff0c;比如&#xff1a;数据库、txt文件、内存缓冲区、邮件、控制台、ANS…

第二节:框架前期准备篇之AutoFac常见用法总结

一. 说在前面的话 凡是大约工作在两年以上的朋友们&#xff0c;或多或少都会接触到一些框架搭建方面的知识&#xff0c;只要一谈到框架搭建这个问题或者最佳用法这个问题&#xff0c;势必会引起一点点小小的风波&#xff0c;我说我的好&#xff0c;他说他的好&#xff0c;非常容…