更多Java知识学习
文章目录
- 前言
- 一、单一职责原则
- 定义:
- 优点:
- 怎么应用?
- 二、开闭原则
- 定义
- 优点
- 怎么应用?
- 三、依赖倒置原则
- 定义
- 优点
- 怎么应用?
- 四、接口隔离原则
- 定义
- 优点
- 怎么应用?
- 五、里氏替换原则
- 定义
- 继承的优缺点
- 什么时候使用继承合适呢?
- 六、迪米特原则
- 定义
- 分析
- 怎么应用?
- 七、合成/聚合复用原则
- 定义
- 前提
- 怎么应用?
- 总结
- 面向对象设计的基本原则
- 设计原则之间的关系
前言
提示:这里可以添加本文要记录的大概内容:
面向对象学习中的这几大原则不可不知道,只有掌握了这些原则,我们才能更好的理解设计模式。
提示:以下是本篇文章正文内容,下面案例可供参考
一、单一职责原则
定义:
一个对象应该只包含单一的职责,并且该职责被完整地封装在一个类中。
也就是说一个类只做一件事或者说只有一个原因可能会引起这个类的变化
优点:
如果没有满足单一原则,一个类会做好多事,这样这个类就会有很多原因会引起他的变化,而且这个类会很难再次被复用;
如果满足了单一原则,一个类只做一件事,相对来讲比较灵活,复用率也比较高
怎么应用?
我们来看一下这个案例
某软件公司开发人员针对CRM(客户关系管理系统)中的客户信息图形统计模块提出了下面的初始设计方案结构图。
仔细分析一下,在这里面有这样一个类,这个类做了哪些事情呢
很显然,他现在已经违背了单一职责的原则
因为承担了过多的职责,这样的设计难以实现代码的重用,会拥有多个引起他变化的原因
所以接下来我们用单一职责原则对他来进行重构:
上面我们分析出他有三个职责,那就由三个类分别来实现这三个职责
用单一职责原则对他进行重构设计:
我们设计三个类,每一个类都有自己的工作
二、开闭原则
定义
一个软件实体(模块,类,函数,等等)应当对扩展开放,对修改关闭。
当我们修改一个软件的需求的时候,可以通过新增代码,也就是扩展的方式来修改,而不能够通过修改代码的方式来修改
优点
因为避免了修改代码的内容,也就避免了因为修改代码而引入新错误的可能
怎么应用?
我们还是用刚刚这个客户关系管理系统
假设现在要对这个客户关系管理系统中的数据库访问功能升级,到Hibernate框架来实现。
我们应该怎样去满足用户的需求
如果是现在上图这种设计去满足客户需求,我们不得不修改代码的内部,但这样就违背了开闭原则
那么怎么样去修改它呢?我们可以将数据访问这里设置一个接口,到底是什么形式的访问,我们可以用具体的类来实现这个接口
那我们客户使用哪种具体的数据访问,我们可以通过配置文件来实现?
首先可以先扩展一个具体的数据访问方式的类,然后我们去修改这个配置就可以了
所以接下来我们用开闭原则对他来进行重构:
重构设计后,再扩展数据库访问方式时,不需修改原实体。
也就是说我们可以通过扩展的方式来满足需求的变更,而不去修改代码的内部
这样就满足了开闭原则:对于扩展是开放的;对于修改是封闭的。
三、依赖倒置原则
定义
抽象不应该依赖于细节,细节应当依赖于抽象。换言之,要针对接口编程,而不是针对实现编程。
优点
如果没有使用依赖倒置原则,当需求发生变更的时候,我们不得不去修改程序代码内部,违背了开闭原则,这样系统灵活性就比较差
如果使用了依赖倒置原则,也就是说我们针对接口编程了,如果用户的需求发生变更,我们添加一个具体实现的类就可以了,也就是说通过扩展的方式实现了用户需求的变更,那么系统的灵活性就比较好
怎么应用?
某软件公司开发人员在开发CRM(客户关系管理系统)时,经常要将存储在TXT或Excel文件中的客户信息转存到数据库中。
针对这一功能有一这原始设计:
每次需要根据数据来源,更换数据转换类
如果要是我们对数据转换方式进行了更新,或者添加了新的转换方式,那是不是要修改代码内部?
所以上图的原始设计违反了开闭原则(每当需求发生变更都要去修改代码),更换数据转换类时,需要修改CustomerDAO;
引入新的数据转换类时,仍需要修改CustomerDAO
所以我们用依赖倒置原则对他进行重构:
依赖倒置原则就是要针对接口编程而不是针对实现编程
现在的原始设计中就针对了TXT类型和Excel类型这两个具体的实现类来编程,所以我们就要提取出来一个接口,做到针对接口编程
我们针对具体的转换类提取出来一个抽象的接口,然后这些具体的转换类型实现这个接口,如果需要改变转换方式,或者添加一种新的转换方式的话,我们只需要修改配置文件就可以了,这样就满足了开闭原则。
四、接口隔离原则
定义
使用多个专门的接口,而不使用单一的总接口。也就是说客户端不应该依赖那些它不需要的接口。
优点
如果我们使用一个很大的、统一的接口的话,那么他将不能够对客户隐藏那些不需要的行为,如果使用了单一的接口,就好比是给客户提供了定制服务
怎么应用?
某软件公司开发人员在开发CRM(客户关系管理系统)时,针对客户数据显示模块设计了下面的接口
那他有哪些职责呢
该接口包含了很多服务
具体的实现类即使不需要也必须实现其中的全部方法破坏了程序的封装性,客户端看到了不该看到的方法。
换言之,当我们客户端需要使用某一个服务的时候,必须要实现整个大的接口,也就是说这里面即使有我不需要的方法,我也必须要把他实现出来;
并且即使我不需要,我也看到了这些方法,没有很好的做到封装和隐藏,所以这样的设计是不合理的
我们要用接口隔离原则对他进行重构:
我们按照刚才分析出来的四个职责,分别为他定制四个接口
这样我们就可以根据自己的需求去实现专门的接口,这样的接口隔离就相当于给客户端提供定制服务,客户端(实现类MobileDisplay)也不会出现无用代码
五、里氏替换原则
定义
所有引用父类的地方必须能透明地使用其子类的对象,话句话说,子类继承父类时不应该改变父类的行为/功能,但可以扩展父类功能(子类可以完全代替父类)
里氏替换原则是针对继承关系进行优化,我们先来想想继承的几个用途:
用作表示两个类之间的共性机制(泛化)
表示一个类是另一个类的特殊形式(特化),子类可以在继承父类的基础上去添加属于自己的属性或方法,子类可以重写父类的方法。
继承的优缺点
优点:实现简单、提高代码重用、提高父类扩展性
缺点:降低代码灵活性、增强了类与类之间的耦合度
什么时候使用继承合适呢?
某家公司里所有的员工都是新员工,每个新员工都有薪水、病假时间和医疗计划。并且有一些适用于新员工的方法,分别为计算税收、计算福利 benefits。
针对该案例我们有个原始设计:
目前新员工这个类里面有三个属性,两个方法
6个月之后,公司进展顺利。决定每位被公司雇佣了6个月的员工被看做老员工,有额外的福利。这些福利包括牙科医疗计划、休假和公司提供的轿车。
在刚才类的基础上,老员工在新员工基础之上多了属于自己的属性,我们就把老员工当做新员工的子类,除了继承原有的属性和方法之外,我们可以对其中一个方法进行扩展代码
公司如此成功,以至于新员工需要花5个月的时间来获悉找谁来报销、找谁签采购单、找谁请病假等。公司决定为新员工准备一次介绍课程 course()。
而老员工显然不需要这个课程。老员工的子类就需要去覆盖这个方法,具体表现为“不上该课程”。
但是里氏替换原则是:子类可以扩展父类功能,但是不能改变父类原有功能,所以目前这种类的结构违反了里氏替换原则
我们用里氏替换原则对他重构设计:
我们把新员工和老员工都当做员工的子类,我们把共同的属性和方法放到员工的父类里面去,这时属于新员工的课程方法由子类去添加成为自己的方法,老员工也可以针对福利的方法去扩展代码
六、迪米特原则
定义
一个软件实体应当尽可能少的与其他实体发生相互作用;
一个类对自己依赖的类知道的越少越好。
俗话说就是不和陌生人说话
迪米特法则包含两种角色:依赖者与被依赖者。
依赖者只依赖应该依赖的对象
被依赖者只暴露应该暴露的方法或者属性
分析
依赖者:只与直接朋友通信(不要和陌生人说话)
两个对象之间的耦合关系我们称之为朋友,关系通常有:依赖、关联、聚合、组合等
直接朋友关系有(关联、聚合、组合)
被依赖者:暴露尽可能少的信息(最少知识原则)
可用private(私有的)就绝不用protected(受保护的);
可用protected(受保护的)就绝不用public(公有的)
怎么应用?
新学期开堂,老师让课代表清点班级学生人数,以下是老师与学生关系的原始结构设计。
在原始设计里面教师类不仅要依赖于课代表类还要和所有学生相依赖
很明显有这种设计违反了迪米特法则,老师和学生之间不需要依赖关系,老师只需要和直接朋友相依赖,迪米特法则也叫不和陌生人说话
我们用迪米特法则对他进行重构设计
迪米特法则说:一个软件实体应当尽可能少的与其他实体发生相互作用。(最少知识原则)
这时我们将教师类和学生类之间的依赖关系解除,这时候只需要和直接朋友课代表相依赖,就能满足掌握所有学生信息的目的
七、合成/聚合复用原则
定义
在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象进行委派,达到复用这些对象的目的。
前提
在面向对象设计中,需要在不同的环境中复用已有的设计和实现
复用的两种方式:
1:继承
通过子类去继承父类 “方法、属性” 完成行为复用
针对继承关系来说:灵活性差、使用环境有限、也叫 “白箱” 复用
2:组合 / 聚合
通过添加 “属性” 完成行为复用
针对组合 / 聚合关系来说:类与类之间的耦合度相对较低、可以选择性的调用成员对象的操作,并且可以在运行的时候动态进行,新对象可以动态的引用与成员对象类型相同的对象,并且这种复用关系支持包装,因为成分对象的内部细节与新对象来说是看不见的,我们也叫黑箱复用
怎么应用?
每个品牌的小轿车价格和外观都不同,通过该案例得出以下原始设计,利用继承完成复用。
想要动态的增加轿车品牌和车的其他表现,这时利用继承会导致代码灵活性很差
想要动态的增加轿车品牌和车的其他表现,利用组合/聚合怎样进行修改?
我们用通过对象的组合方式来完成品牌的增加,从而来完成功能的复用,能够在运行的过程中动态的增加行为,使得我们的灵活性、可变性都会提高
总结
面向对象设计的基本原则
单一职责原则(SRP)
类的职责要单一。
“开——闭”原则(OCP)
针对可变因素进行抽象化。
里氏替换原则(LSP)
继承层面、子类型必须能够替换掉他们的父类型。
依赖倒转原则(DIP)
引入抽象模块、针对接口编程。
接口隔离原则(ISP)
合理划分接口。
Demeter法则(LoD)
不要跟陌生人说话、最少知识原则。
合成/聚合复用原则(CARP)
尽量使用组合/聚合/关联、尽量不使用继承。
设计原则之间的关系
开闭原则应用单一职责原则,其关键是抽象
里氏替换原则是开闭原则的补充,是实现抽象的步骤规范
依赖倒置是实现开闭原则的手段
接口隔离原则前提是单一职责原则