开闭原则
开闭原则(Open-Closed Principle, Ocp)是指类、模块、函数等软件实体,对扩展开放,对修改关闭。
强调用抽象构建框架,用实现扩展功能,提高软件系统的可复用性及可维护性。
开
即为开放扩展,扩展意为增加新的代码
闭
即为关闭修改,修改意为修改现有的代码
实现方式
如果有新的业务需求或者业务需求需要修改,尽量要新增代码。如果想修改现有的功能,那就新增一个方法;如果想修改一个类,那就新增一个类;
实例
以课程体系为例,首先创建一个课程接口ICourse:
public interface Icourse{
Integer getId();
String getName();
BigDecimal getPrice();
}
将我们原有的课程都实现该接口:
public class MathCourse implements ICourse{
private Integer Id;
private String name;
private BigDecimal price;
//获取价格
public BigDecimal getPrice(){
return this.price;
}
}
现在需要给数学课做活动,加个优惠,不能修改原来的getPrice()方法影响原本的业务逻辑。那么,我们就再写一个新的类,继承重写父类的getPrice()方法
public class MathDiscountCourse extends MathCourse(){
public BigDecimal getOriginPrice(){
return super.getPrice();
}
public BigDecimal getPrice(){
return super.getPrice() * 0.5;
}
}
依赖倒置原则
依赖倒置原则(Dependence Inversion Principle, DIP)是指设计代码结构时,高层模块不应该依赖底层模块,二者都应该依赖抽象。
抽象不依赖细节,细节依赖抽象,通过这种方式,减少类与类之间的耦合性,降低修改造成的风险
实例
public class Tom{
public void studyMathCourse(){
println("Tom 学习 数学");
}
public void studyEnglishCourse(){
println("Tom 学习 英语");
}
}
调用:
public static void main(String[] args){
Tom tom = new Tom();
tom.studyMathCourse();
tom.studyEnglishCourse();
}
依赖注入
如果Tom需要学习新的课程时,需要再Tom类中添加新的学习课程的方法,造成了修改代码的风险,根据依赖倒置原则,我们通过接口的方式实现:
public interface ICourse{
void study();
}
public class MathCourse implements ICourse {
@Override
public void study(){
println("Tom 学习 数学");
}
}
public class EnglishCourse implements ICourse {
@Override
public void study(){
println("Tom 学习 英语");
}
}
//Tom类
public class Tom {
public class study(ICourse course){
course.study();
}
}
//调用
public static void main(String args[]){
Tom tom = new Tom();
tom.study(new MathCourse());
tom.study(new EnglishCourse());
}
通过这种方式,无论课程如何增加,只要通过传参方式调用方法,就不需要修改代码,该种方式叫做依赖注入
构造器注入
public class Tom {
private ICourse course;
public Tom(ICourse course){
this.course = course;
}
public void study(){
corse.study();
}
}
//调用
public static void main(String[] args){
Tom tom = new Tom(new MatCourse());
tom.study();
}
如果ICourse是全局单例,那么可以采用这种方式,注意该方式线程不安全。如果对线程安全有要求,可采用双端锁单例
单一职责原则
单一职责(Simple Responsibility Pinciple,SRP)是指不要存在多于一个导致类变更的原因。
如果一个类Class1处理业务T1和业务T2,当业务T1改变时,需要修改Class1的代码,此时对T2造成了一定风险。所以遵循单一职责原则,Class1只处理T1,再编写Class2处理T2,降低修改带来的风险
总体来说,就是一个Class/Interface/Method 只负责一项职责
实例
假设我们有两种学习课程的方式,观看直播课和观看录播课。
public class Course{
public void study(String courseName){
if(courseName.equals("直播课")){
println("不能快进");
}else{
println("可以快进");
}
}
}
//调用
public static void main(String args[]){
Course course = new Course();
course.study("直播课");
course.study("录播课");
}
该课程类承担了直播课和录播课两种职责,当其中一种课程业务逻辑需要更改时,会对另一种课程造成风险。
根据单一设计原则,我们将其设计为两个类
//录播课
public class ReplayCourse{
public void study(){
println("可以快进");
}
}
//直播课
public class LiveCourse{
public void study(){
println("不能快进");
}
}
//调用
public static void main(String args[]){
LiveCourse liveCourse = new LiveCourse();
liveCourse.study();
ReplayCourse replayCourse = new ReplayCourse();
replayCourse.study();
}
根据业务发展,可能需要扩充一些方法实现功能,假设需要补充两方面的方法:
- 课程本身的信息(课程名称、介绍、类型等)
- 课程的权限管理(购买、观看、退课等)
此时,我们再设计一个顶层接口ICourse,定义这些方法
public interface ICourse{
//查看课程描述
String getInfo();
//购买课程
void buyCourse();
}
根据单一设计原则,我们又可以将其拆分为两个接口
//课程本身的信息
public interface ICourseInfo{
//查看课程描述
String getInfo();
}
//课程权限管理
public interface ICourseManage{
//购买课程
void buyCourse();
}
根据这种设计思想,方法级、类级、库级、项目级在业务较为复杂时,尽量满足单一设计原则,可以更好的维护项目
接口隔离原则
接口隔离原则(Interface Segregation Principle, ISP)是指用多个专门的接口,而不使 用单一的总接口,客户端不应该依赖它不需要的接口。
- 一个类对一类的依赖应该建立在最小的接口之上
- 建立单一接口,不要建立庞大臃肿的接口
- 尽量细化接口,接口中的方法尽量少
举例
public interface IAnimal{
//跑步
void run();
//飞
void fly();
//游泳
void swim();
}
当需要编写一些动物类实现该接口时,不得不继承该接口的全部方法,但是不是每个动物类都需要该接口中的全部方法
根据接口隔离原则,将IAnimal接口再进行拆分
public interface FlyAnimal{
//飞
void fly();
}
public interface RunAnimal{
//跑
void run();
}
public interface SwimingAnimal{
//游泳
void swim();
}
通过这种方式,在编写动物类时,只继承需要的接口,避免重写不需要的方法,减少接口间的耦合性
迪米特法则
迪米特原则(Law of Demeter LoD)是指一个对象应该对其他对象保持最少的了解,又 叫最少知道原则(Least Knowledge Principle,LKP),尽量降低类与类之间的耦合。
实现
减少类与类之间不必要的依赖
里氏替换原则
为一个软件实体如果适用一个 父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对 象,子类对象能够替换父类对象,而程序逻辑不变。
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类中可以增加自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类 方法的输入参数更宽松。
- 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即 方法的输出/返回值)要比父类更严格或相等。