“设计模式之禅”——六大设计原则详解解读
目录
一、单一职责原则
二、里氏替换原则
三、依赖倒转原则
四、接口隔离原则
五、迪米特法则
六、开闭原则
一、单一职责原则
单一职责原则的英文名称是:Single Responsibility Principle,简称SRP。它的内容是:应该有且只有一个引起类变化的原因。
例如下面这个类,它的设计就违反了单一职责原则:
经过职责划分后的类图如下:
重新拆封成两个接口,IUserBO负责用户的属性,简单地说,IUserBO的职责就是收集和反馈用户的属性信息;IUserBiz负责用户的行为,完成用户信息的维护和变更。
分清职责后的代码示例:
......
IUserInfo userInfo = new UserInfo();
//我要赋值了,我就认为它是一个纯粹的BO
IUserBO userBO = (IUserBO)userInfo;
userBO.setPassword("abc");
//我要执行动作了,我就认为是一个业务逻辑类
IUserBiz userBiz = (IUserBiz)userInfo;
userBiz.deleteUser();
......
采用SRP的类图如下:
单一职责原则的好处:
- 类的复杂性降低,实现什么职责都有清晰明确的定义
- 可读性提高,复杂性降低
- 可维护性提高,可读性提高
- 变更引起的风险降低
单一职责适用于接口、类,同时也适用于方法。
二、里氏替换原则
里式替换原则的英文名称是:Liskov Substitution Principle,简称LSP。它的内容是:所有引用基类的地方必须能透明地使用其子类的对象。通俗地讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常。
里式替换原则为良好的继承定义了一个规范,它包含了4层含义:
(1)子类必须完全实现父类的方法
例如下面这个例子,模拟射击游戏的枪支类:
设计一个枪支抽象类,然后具体的枪支继承这个抽象类,并实现对应的shoot方法。
public abstract class AbstractGun {public abstract void shoot();
}
public class HandGun extends AbstractGun {@Overridepublic void shoot() {System.out.println("手枪设计...");}
}//Rifle、MachineGun省略
在士兵类中,使用枪来杀敌,但是这个枪是抽象的,具体是什么类型的枪需要通过setGun方法来确定。
public class Soldier {private AbstractGun gun;public void setGun(AbstractGun gun) {this.gun = gun;}public void killEnemy() {System.out.println("士兵开始杀敌人...");gun.shoot();}
}
public class Client {public static void main(String[] args) {Soldier soldier = new Soldier();soldier.setGun(new HandGun());soldier.killEnemy();}
}
在类中调用其他类时务必使用父类或者接口,如果不能使用父类或者接口,则说明类的设计已经违背了LSP原则。
下面定义一个玩具枪类:
如果还是使用原来的继承方式,由于玩具枪是不能射击的,所以此时的shoot方法就“没用”了。这就导致了正常的业务逻辑不能运行了。此时,有两种解决方法:
1)在Soldier类中增加instanceof的判断,如果是玩具枪就不能用来杀敌人。但是,这种修改每增加一个类就必须修改,所以不提倡。
2)将ToyGun脱离继承,建立一个独立的父类,并与AbstractGun建立关联委托关系。
如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。
(2)子类可以有自己的个性
还是上面的例子,假设增加AUG狙击枪和狙击手类,此时除了实现shoot方法之外,还可以增加一些自己特有的方法。
public class AUG extends Rifle {public void shoot() {System.out.println("AUG射击...");}public void zoomOut() {System.out.println("通过瞄准镜观察敌人...");}
}
public class Snipper {private AUG aug;public void setGun(AUG aug) {this.aug = aug;}public void killEnemy() {aug.zoomOut();aug.shoot();}
}
(3)重写或者实现父类的方法时输入参数可以被放大
当我们重载父类中的方法的时候(注意:重载(Overload)父类的方法而不是重写(Override)),必须要满足子类中方法的前置条件(参数)必须与超类中被重载的方法的前置条件相同或者更宽松。
public class Father {public Collection doSomething(HashMap map) {System.out.println("父类被执行...");return map.values();}
}
public class Son extends Father {//放大输入参数类型public Collection doSomething(Map map) {System.out.println("子类被执行...");return map.values();}
}
最终的结果和使用父类时的打印结果是一样的。
public class Client {public static void invoker() {//父类存在的地方,子类就应该存在//Father f = new Father();Son f = new Son();HashMap map = new HashMap();f.doSomething(map);} public static void mian(String[] args) {invoker();}
}//结果:父类被执行...
除非重写父类中的方法,否则,子类代替父类传递到调用者中,子类的方法永远不会执行。
(4)重写或者实现父类的方法时输出结果可以被缩小
同理,当我们重载父类中的方法的时候,必须要满足子类中方法的后置条件(返回值)必须与超类中被重载的方法的后置条件相同或者更狭窄。
总而言之,采用里式替换原则的目的就是增强程序的健壮性,版本升级时也可以保持非常好的兼容性。即使增加子类,原有的子类还可以继续运行。
三、依赖倒转原则
依赖倒转原则的英文名称是:Dependence Inversion Principle,简称DIP。它主要有三层含义:
- 高层模块不应该依赖于底层模块,两者都用该依赖其抽象;
- 抽象不应该依赖细节;
- 细节应该依赖抽象。
依赖倒转原则在Java语言中的表现就是:
- 模块间的依赖通过抽象发生,实现类之间不发生直接的依赖关系,其依赖关系是通过接口和抽象类产生的;
- 接口或抽象类不依赖于实现类;
- 实现类依赖接口或抽象类。
对象的依赖关系有三种写法来传递:
(1)构造函数传递依赖对象
(2)Setter方法传递依赖对象
(3)接口声明依赖对象
如下面这个例子,按照下面这种设计就不符合依赖倒转原则,具体的汽车类应该依赖一个抽象类。
经过修改后的类图如下:
四、接口隔离原则
接口隔离原则的定义如下:
(1)客户端不应该依赖它不需要的接口;
(2)类间的依赖关系应该建立在最小的接口上。
概括为一句话为:建立单一接口,不要建立臃肿庞大的接口。同时,接口尽量细化,同时接口中的方法尽量少。
例如下面这个例子,定义了一个IPetteyGirl接口,声明所有的PetteyGirl的标准是goodLooking、niceFigure和greatTemperament,然而实际的情况并不是这样,显然这个接口设计的过于庞大了。
修改后的类图如下:
接口隔离原则和单一职责原则的区别:
- 单一职责原则要求的是类和接口的职责要一致
- 接口隔离原则要求接口中的方法尽量少
接口隔离原则对接口进行规范约束,包含了以下4层含义:
(1)接口要尽量小
但是“小”是有限度的,首先必须满足单一职责原则。
(2)接口要高内聚
高内聚就是提高接口、类、模块的处理能力,减少对外的交互。
(3)定制服务
定制服务就是单独为一个个体提供优良的服务,因此在接口设计时要求:只提供访问者需要的方法。
(4)接口的设计是有限度的
接口的设计粒度越小,系统越灵活。但是,灵活的同时也带来了结构的复杂性,开发难度增加,可维护性降低。
五、迪米特法则
迪米特法则的英文名称是:Law of Demeter,简称LoD,也称最少知识原则。它的规则是:一个对象应该对其他的对象有最少的了解。通俗的讲,一个类应该对自己需要耦合或调用的类知道得最少。
如下面这个例子:老师希望体育委员统计女生的数量,在这个场景下,显然老师不需要与女生产生依赖关系,只需要与体育委员依赖即可。而在原来的类的设计中,老师和体育委员与女生同时存在依赖关系,这显然不符合迪米特法则。
public class Teacher {public void commond(GroupLeader groupLeader) {List<Girl> listGirls = new ArrayList();//初始化女生for(int i = 0; i < 20; i++) {listGirls.add(new Girl());}groupLeader.countGirls(listGirls);}
}
public class GroupLeader {public void countGirls(List<Girl> listGirls) {System.out.println("女生数量是:" + listGirls.size());}
}
根据迪米特法则进行如下修改:
public class Teacher {public void commond(GroupLeader) {groupLeader.countGirls(listGirls);}
}
public class GroupLeader {private List<Girl> listGirls;public GroupLeader(List<Girl> listGirls) {this.listGirls = listGirls;}public void countGirls() {System.out.println("女生数量是:" + listGirls.size());}
}
迪米特法则对类的低耦合提出了明确的要求,包含以下4中含义:
(1)“只和朋友交流”
如果两个对象耦合,那么它们就是“朋友”关系。一个类只和朋友交流,不与陌生类交流。不要出现getA().getB()这种情况,类与类之间的关系是建立在类间的,而不是方法间。因此,一个方法尽量不引入一个类中不存在的对象(JDK API提供的方法除外)。
(2)“朋友间也是有距离的”
尽量不要对外公布太多public方法和非静态的public变量,多使用private、package-private、protected等访问权限。
(3)“是自己的就是自己的”
如果一个方法放在本类中,既不增加类间关系,也对本类不产生负面影响,那就放置在本类中。
(4)谨慎使用Serializable
六、开闭原则
开闭原则是六大设计原则中最基础的设计原则。它的定义是:一个实体(如类、模块或者函数)的设计应该对扩展开放,对修改关闭。
开闭原则的核心思想是面向抽象编程。通过接口或抽象类来约束一组可能变化的行为,从而实现对扩展开放,任何时候我们都不会直接去修改原来类中的内容,从而实现了对修改关闭。
如下面这个例子,如果我们希望再想实现图书“打折”的效果,我们不应该直接去增加一个setPrice的方法或者再原有的getPrice方法中进行修改,正确的做法是:创建一个打折类,并且这个类继承自NovelBook类,通过重写打折类中的getPrice方法来实现。
public class OffNovelBook extends NovelBook{public OffNovelBook(String name,int price,String author){super(name,price,author);}//覆写价格方法,当价格大于40,就打8析,其他价格就打9析public int getPrice(){if(this.price > 40){return this.price * 0.8;}else{return this.price * 0.9;} }
}
开闭原则包含三层含义:
(1)通过接口或抽象类来约束扩展,对扩展进行边界限定,不允许出现在接口或者抽象类中不存在的public方法;
(2)参数类型、引用类型尽量使用接口或者抽象类,而不是实现类;
(3)抽象类尽量保持稳定,一旦确定即不允许修改。
开闭原则的重要性:
- 面向对象的要求
- 提高代码的复用性
- 提高代码的可维护性
- 便于进行单元测试
“设计模式之禅”——六大设计原则详解解读相关推荐
- 设计模式之禅(六大设计原则)
1.单一职责原则(Single Responsibility Principle) 也就是职责划分要明确,单一职责原则提出了一个编写程序的标准,用"职责"或者"变化原因& ...
- 引用防删——JAVA设计模式总结之六大设计原则
JAVA设计模式总结之六大设计原则 从今年的七月份开始学习设计模式到9月底,设计模式全部学完了,在学习期间,总共过了两篇:第一篇看完设计模式后,感觉只是脑子里面有印象但无法言语.于是决定在看一篇,到9 ...
- java设计模式总结之六大设计原则(有图有例子)
转载:https://www.cnblogs.com/jpfss/p/9765239.html 下面来总结下自己所学习的设计模式,首先我们看下各个模式之间的关系图,下面这张图是网上比较典型的一个类图关 ...
- JAVA设计模式总结之六大设计原则(一)
从今年的七月份开始学习设计模式到9月底,设计模式全部学完了,在学习期间,总共过了两篇:第一篇看完设计模式后,感觉只是脑子里面有印象但无法言语.于是决定在看一篇,到9月份第二篇设计模式总于看完了,这一篇 ...
- 设计模式系列,六大设计原则
设计模式和性能优化有没有关系?最近,我看到有人再讲性能优化的时候,讲到了"有些设计模式可以做到一定程度的性能优化". 我读书少,别骗我.我看过无数篇设计模式了,第一次听到有人说,设 ...
- 举例说明层次分析的三大原则_设计模式系列,六大设计原则
设计模式和性能优化有没有关系?最近,我看到有人再讲性能优化的时候,讲到了"有些设计模式可以做到一定程度的性能优化". 我读书少,别骗我.我看过无数篇设计模式了,第一次听到有人说,设 ...
- 设计模式必备知识点----六大设计原则
六大设计原则 一,开闭原则 开闭原则的定义 什么是开闭原则 开闭原则的作用 开闭原则的优点 二,单一职责原则 单一职责定义 单一职责的作用 单一职责的优点 单一职责的违背原则 三,依赖倒置原则 依赖倒 ...
- Java 设计模式总结及六大设计原则
设计模式总结 总体来说设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式. ...
- Java设计模式中的六大设计原则
最近一直在看有关设计模式的博客和文章,发现自己对于设计模式的认识和理解还是有点浅显,于是想动手写博客巩固一下. 在开始阐述设计模式之前,首先介绍一下设计模式中的六大原则: 总原则-开闭原则 ...
最新文章
- C语言输入3298运行结果为,浙江农林大C语言程序设计习题集答.doc
- LeetCode: 150:逆波兰表示法求值。
- 【SSH高速进阶】——struts2简单的实例
- Android 8.0 学习(14)---Android8.0适配分析
- 模式代码 java中aes_深入浅出:Java中的代理模式
- 平板直撑的腰椎问题(塌腰)
- 安装包制作工具 SetupFactory使用 详解
- 图像拼接算法总结(一)
- 完美解决VS2003.Net fatal error LNK1201: 写入程序数据库“.pdb”时出错我的开发环境是Win7旗舰64位+VS2003.Net,经常卡pdb错误,文末给出一个完美的解决
- 圆形头像制作,仿QQ做法。
- Team Foundation Server 开发流程管理管理研讨会
- Python 一键导出微信读书的书籍和笔记
- 报错:Torch not compiled with CUDA enabled看这一篇就足够了
- SQLServer触发器的使用
- 厚积薄发 | 游戏引擎十年技术点滴
- 我的世界1.12.2 神奇宝贝(精灵宝可梦) 开服教程
- 架构师进阶之路——1、持久化框架(一)
- React-18--css in js
- Material studio 2017R2生成的模型文件导入WIN10中lammps遇到的小问题
- 【C语言】练习:给出三角形三边长,求三角形面积