JAVA设计模式提供六个基本原则,分别是:
开闭原则(OCP) - The Open-Closed Principle
单一职责原则(SRP) - Single Responsibility Principle
里氏替换原则(LSP) - Liskov Substitution Principle
依赖倒置原则(DIP) - Dependency Inversion Principle
接口隔离原则(ISP) - Interface Segregation Principle
迪米特法则(DP) - Demeter Principle
类的变更的原因应该只有一个,它提出用“职责”和“变化原因”来衡量接口或类设计得是否优良,但是这两个因素都是因项目而异因环境而异的,并没有一个量化的标准;
比如一个用户的实体
它的用户行为和属性没有分开,应该把用户的信息抽取成一个业务对象,把行为抽取成为一个业务逻辑,修改完成后如图
职责划分之后IUserBo负责用户的属性,IUserBiz负责用户的行为;
****这种类的职责划分很简单,但是在一些复杂的场景就并不是很容易了,单一职责最难划分的就是这儿,一个职责一个接口,但是职责没有一个量化的标准,一个类到底要负责那些这侧,这些职责该怎么细化,细化后是否又要有一个接口或类,相同的职责放到一起,不同的职责分解到不同的接口和实现中去,这个是最容易也是最难运用的原则,关键还是要从业务出发,从需求出发,识别出同一种类型的职责。;
【例】大学学生工作管理程序。
分析:大学学生工作主要包括学生生活辅导和学生学业指导两个方面的工作,其中生活辅导主要包括班委建设、出勤统计、心理辅导、费用催缴、班级管理等工作,学业指导主要包括专业引导、学习辅导、科研指导、学习总结等工作。如果将这些工作交给一位老师负责显然不合理,正确的做 法是生活辅导由辅导员负责,学业指导由学业导师负责,其类图如图
大学学生工作管理程序的类图
父类可被子类替换,但反之不一定成立,里氏替换为良好的继承定义了一个规范。比如:
List<> list = new ArrayList<>();
注意:
- 应该面向接口编程,不该面向实现类编程。
接口尽量细化
接口中的方法和属性都是实现类所需要,没有额外无用信息,拆分接口是要满足单子职责原则。
也可以实现多个接口来满足需求,接口隔离原则宗旨是接口的完美复用,接口的内容对于实现来说无冗余代码。
接口要高内聚
尽量少公布PUBLIC方法,接口是对外的承诺,接口越少对系统的开发越有利,变更的风险也越少。比如我要我要下属明天下午一点之前搞一个文件,到时间文件就放到了我的桌子上,具体什么文件怎么写我都不用管。
定制服务
单独为一个个体提供接口,如果一个模块除了已有的接口之外还需要另一个功能,再提供一个单独的服务,而不是把这个服务加到那个接口上。
接口设计有限
不要过多的设计接口
理解
一个类应该对自己需要耦合或调用的类知道的越少越好,你内部是如何复杂和我都没关系,我就知道你提供这么多public方法,我就调用这么多,其他一概不关心。
*朋友类的定义:出现在成员变量、方法的输入输出参数中的类成为成员朋友类,而出现在方法体内部的类不属于朋友类;
例如下面代码中Teacher的朋友类是GroupLeader,listGirls不是朋友类:
/**
* 老师让体育委员清点女生数量
**/
public class GroupLeader {
//清查女生数量
public void countGirls(List listGirls){
System.out.println("女生数量是:"+listGirls.size());
}
}
---------------------------------------------------------------------
public class Teacher {
//老师对学生发布命令,清点女生
public void commond(GroupLeader groupLeader){
List listGirls = new ArrayList();
//初始化女生
for(int i=0;i<20;i++){
listGirls.add(new Girl());
}
//告诉体育委员开始执行清查任务
groupLeader.countGirls(listGirls);
}
}
上面代码应该将女生的初始化放入GroupLeader中,代码修改为:
public class Teacher {
//老师对学生发布命令,清一下女生
public void commond(GroupLeader groupLeader){
//告诉体育委员开始执行清查任务
groupLeader.countGirls();
}
}
---------------------------------------------------------------------
public class GroupLeader {
private List listGirls;
//传递全班的女生进来
public GroupLeader(List _listGirls){
this.listGirls = _listGirls;
}
//清查女生数量
public void countGirls(){
System.out.println("女生数量是:"+this.listGirls.size());
}
}
和朋友类之间保持距离
例如我们在安装软件的时候,经常会有一个导向动作,第一步是确认是否安装,第二步确认License,再然后选择安装目录……这是一个典型的顺序执行动作,具体到程序中就是:调用一个或多个类,先执行第一个方法,然后是第二个方法,根据返回结果再来看是否可以调用第三个方法,或者第四个方法,等等,其类图如图
很简单的类图,实现软件安装的过程,其中first方法定义第一步做什么,second方法定义第二步做什么,third方法定义第三步做什么,其实现过程如代码清单5-8所示。
public class InstallSoftware {
public void installWizard(Wizard wizard){
int first = wizard.first();
//根据first返回的结果,看是否需要执行second
if(first>50){
int second = wizard.second();
if(second>50){
int third = wizard.third();
if(third >50){
wizard.first();
}
}
}
}
}
不难发现,InstallSoftware对Wizard 的耦合太高了,需要修改,应该将installWizard方法放入Wizard 中,Wizard 前三个方法改为私有方法,InstallSoftware仅调用Wizard.installWizard()方法。
总结:迪米特法则的核心观念就是类间解耦,弱耦合,只有弱耦合之后,类的复用率才可以提高。
但是解耦是有限度的,在实际使用中需要适度地考虑这个原则。
理解
类、模块和函数应该通过扩展来实现变化,而不是修改已有的代码,
例子
比如模拟书店卖书,类图如下
现在书全部都是原价出售的,如果我想打折,则不能对原有代码进行修改,应该再扩展一个类继承原有的NovelBook,然后覆盖getPrice,OffNovelBook.getPrice()为打折价格
开闭原则是最基础的一个原则,前面五个原则就是指导设计的工具和方法,而开闭原则才是精神领袖。
使用:
通过接口或抽象类可以约束一组可能变化的行为,并且能够实现对扩展开放。
软件设计最大的难题就是应对需求的变化,但是纷繁复杂的需求变化又是不可预料的。我们要为不可预料的事情做好准备,这本身就是一件非常痛苦的事情,但是大佬们还是给我们提出了6大设计原则以及23个设计模式来“封装”未来的变化。我们可以灵活运用这些东西来解决我们的需求。