软件架构设计原则
开闭性原则
什么是开闭性原则?就是我们已经写好的代码,对修改关闭,对扩展开放。
你就比如,公司是九点半上班,晚上六点半下班,你可以早来,可以晚走,这是可以扩展的。但是你不能晚来,早走,你不能在原有的逻辑上进行修改。
依赖倒置原则
细节通过传参进行控制,大的行为通过一个方法进行控制。你比如,现在有一个Tom类,如下
public class Tom{
public void study(ICourse course){
course.study();
}
}
Tom的大的行为就是学习课程,这个大的行为我们使用一个方法study进行控制,而学习课程的一些细节,就是你学习的到底是什么课程,比方说你是学习java,还是学习python,又或者是学习AI算法,都可以通过传递不同的参数进行控制。我们只需要写一些实现类JavaCourse,PythonCourse,AICourse,让这些实现类都实现ICourse接口,然后把这些实现类当做参数传递给study方法就行了。
这样无论Tom想要学习什么课程,都可以通过传递不同的实现类参数给study来实现。这就叫做依赖倒置原则。
如果没有这个依赖倒置原则,你想一下会出现什么后果,就是如果Tom的学习兴趣很浓烈的话,那么每学习一门课程,你就需要要在Tom类里面定义一个方法,比如上面的这个例子,需要在Tom类里面定义三个方法,分别是JavaCourseStudy方法,PythonCourseStudy方法,AICourse方法,这样是不是就显得Tom这个类里面显得特别臃肿,所以这就是依赖倒置原则的好处。
单一职责原则
单一职责原则意思就是:一个类,接口,方法只负责一项职责。
先来说一下方法只负责一项职责是什么意思,就是一个方法中只能有一项功能,如果这个方法中的某一段很长的代码可以被单独抽离成一个功能,那么我们就需要把这段代码单独封装成一个功能。
接口只负责一项职责是什么意思,就是一个接口里面的抽象方法必须要属于同一个大类的功能。比如有专门获取信息的接口,有专门管理信息的接口,我们要写成两个接口,不能把它们写在一个接口里面。
类只负责一项职责是什么意思,我们的类实现了只负责一项职责的接口,那么它肯定也是只负责一项职责。
接口隔离原则
接口隔离原则的意思就是每一个接口必须要是一个最小单位,什么意思呢?
你就比如现在有三个接口:
//可以吃的动物接口
public interface IEatAnimal{
void eat();
}
//可以飞的动物接口
public interface IFlyAnimal{
void fly();
}
//可以游泳的动物接口
public interface ISwimAnimal{
void swim();
}
如果我们现在有一个小鸟Bird,这个小鸟既可以飞又可以吃,但是不可以游泳,那么我们就可以写成这种格式:
public class Bird implements IEatAnimal,IFlyAnimal{
@Override
void eat(){}
@Override
void fly(){}
}
上面定义的三个接口就是最小单位的接口。
那怎么判断接口不是最小单位的接口呢?你就想一下,如果一个实体类实现了一个接口,这个接口中的抽象方法会不会有得不到继承的。比如现在有一个动物接口,如下:
public interface IAnimal{
void eat();
void fly();
void swim();
}
然后一个小鸟Bird来实现这个接口,那么就会出现一个问题,因为swim()小鸟是做不到的,所以就会出现一个用不着实现的多余方法,那么IAnimal这个接口就不是最小单位的接口。
迪米特原则
迪米特原则的意思就是:只和熟悉的人交流,不和陌生人交流。
其实也就是不要跨层管理,上层就只负责它的直属下层就行了,不要负责它的直属下层的直属下层。
你比如,现在有一个Boss要统计课程的数量,它需要去问TeamLeader,然后TeamLeader会去统计课程的数量。如果Boss不仅要管他的直属下层TeamLeader还要管它的直属下层的直属下层,就会出现下面的这个情况,如下:
//Boss类的代码如下
public class Boss{
public void commandCheckNumber(TeamLeader teamLeader){
//模拟Boss一页一页往下翻页,TeamLeader实时统计
List<Course> courseList = new ArrayList<Course>();
for(int i=0; i<20; i++){
courseList.add(new Course());
}
teamLeader.checkNumberOfCourse(courseList);
}
}
//TeamLeader类的代码如下
public class TeamLeader{
public void checkNumberOfCourse(List<Course> courseList){
System.out.println("目前已经发布的课程数量是:"+courseList);
}
}
乍一看上面的代码是没有问题的,但是你仔细分析可以发现,Boss类它不仅和它的直属下层TeamLeader进行了交流,还和它的直属下层的下层Course进行了交流,这样就不符合迪米特原则了。我们可以进行一些修改,让Boss类只和它的直属下层TeamLeader进行交流,不要和Course进行交流,至于和Course进行交流的工作应该分给TeamLeader,如下:
//Boss类的代码如下
public class Boss{
public void commandCheckNumber(TeamLeader teamLeader){
teamLeader.checkNumberOfCourse(courseList);
}
}
//TeamLeader类的代码如下
public class TeamLeader{
public void checkNumberOfCourse(List<Course> courseList){
List<Course> courseList = new ArrayList<Course>();
for(int i=0; i<20; i++){
courseList.add(new Course());
}
System.out.println("目前已经发布的课程数量是:"+courseList);
}
}
对应到我们的项目中就是,我现在能想到的就是一个三层架构,上层直接去调用它的下层,但不能出现即调用它的下层又调用它的下下层的情况。
里式替换原则
什么是里式替换原则呢?意思就是所有用到父类的地方,都可以用它的子类替换。引申含义其实是:子类可以扩展父类的功能,但不可以改变父类原有的功能。有两个规范:
1.子类可以实现父类中的抽象方法,但是不能重写父类中的非抽象方法,父类中的非抽象方法,子类中必须要保持父类中原有的非抽象方法的完整性。
2.子类中可以增加自己特有的方法。
比如,如果子类中重写了父类的非抽象方法,当用子类替换父类的时候,可能就出现异常了,如下:
有一个长方形类:
/** * @Date 2022/1/31 12:31 * @Author 望轩 */ public class Rectangle { private long height; private long width; public long getWidth(){ return width; } public long getHeight(){ return height; } public void setHeight(long height){ this.height = height; } public void setWidth(long width){ this.width = width; } }
有一个正方形类继承长方形类,但是正方形类重写了父类中的非抽象方法setHeight和setWidth,如下:
/** * @Date 2022/1/31 12:35 * @Author 望轩 */ public class Square extends Rectangle { @Override public long getWidth() { return super.getWidth(); } @Override public long getHeight() { return super.getHeight(); } @Override public void setHeight(long height) { super.setHeight(height); super.setWidth(height); } @Override public void setWidth(long width) { super.setWidth(width); super.setHeight(width); } }
然后写了一个测试类,如下:
/** * @Date 2022/1/31 12:37 * @Author 望轩 */ public class Test { public static void main(String[] args) { Rectangle rectangle = new Rectangle(); rectangle.setWidth(20); rectangle.setHeight(10); resize(rectangle); } //当长方形的宽大于等于高的时候循环,让长方形高加一 public static void resize(Rectangle rectangle){ while (rectangle.getWidth() >= rectangle.getHeight()){ rectangle.setHeight(rectangle.getHeight()+1); System.out.println("width:"+rectangle.getWidth()+",height:"+rectangle.getHeight()); System.out.println("resize方法结束"+"\nwidth:"+rectangle.getWidth()+",height:"+rectangle.getHeight()); } } }
当测试类中的resize方法的参数是Rectangle父类的时候,测试方法可以成功执行。那么按照里式替换原则,我们可以把父类替换成它的子类,仍然可以成功的执行,但是当我们把这里的父类对象Rectangle替换成它的子类Square之后,测试方法就不能成功执行了,会出现死循环,因为我们的Square子类覆盖了父类的非抽象方法setHeight和setWidth,导致width和height都是同步变化的,所以while循环会一直循环下去。
因此,根据里式替换原则,子类一定不能覆盖父类的原有的方法,但子类可以增加自己独有的方法。
所以像上面的这种,正方形,长方形的例子,我们要重新写一个接口,把正方形和长方形共有的东西给抽象出去,然后再让正方形,长方形分别重写这个接口,然后实现不同的方法逻辑,如下:
//定义一个四边形接口 public interface Quadrangle{ void setWidth(long width); void setHeight(long height ); } //正方形实现这个接口 public class Square implements Quadrangle{ private long width; private long height; @Override void setWidth(long width){ this.width = width; this.height = height; } @Override void setHeight(long height){ this.height = height; this.width = width; } } //长方形实现这个接口 public class Rectangle implements Quadrangle{ private long width; private long height; @Override void setWidth(long width){ this.width = width; } @Override void setHeight(long height){ this.height = height; } }