Spring学习01-Spring中的架构设计原则
1.1 开闭原则(Open-Closed Principle)
开闭原则是指一个软件实体应对扩展开发,对修改关闭;其含义是说一个软件实体应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化。
开闭原则主要是支持每个实体类都可以支持对他的扩充,但不允许直接修改其内容。
Instance
/**
* @author Xinyuan
*/
public interface IBook {
Integer getISBN();
String getName();
Double getPrice();
}
public class JavaBook implements IBook{
private Integer ISBN;
private String Name;
private Double Price;
public JavaBook(Integer ISBN, String name, Double price) {
this.ISBN = ISBN;
Name = name;
Price = price;
}
@Override
public Integer getISBN() {
return ISBN;
}
@Override
public String getName() {
return Name;
}
@Override
public Double getPrice() {
return Price;
}
}
首先我们定义了一个书本的接口,再定义一个JavaBook的实现类,用于网上商城售卖书本。
但此时商场想要做活动,对JavaBook进行打8折的促销,根据开闭原则,我们无法修改原有的代码,因为如果直接修改可能会影响其他地方调用该类的结果。
所以此时我们需要再编写一个JavaDiscountBook的的子类来实现。
public class JavaDiscountBook extends JavaBook{
public JavaDiscountBook(Integer ISBN, String name, Double price) {
super(ISBN, name, price);
}
@Override
public Double getPrice() {
return super.getPrice() * 0.8;
}
}
OK,此时我们就完成了按照开闭原则进行的新业务的处理
1.2 依赖倒置原则(Dependence Inversion Principle)
依赖倒置原则是指设计代码结构的时候,高层模块不应该依赖底层模块,但二者都可依赖其抽象。抽象不应该依赖细节,细节不应该依赖抽象。
依赖倒置可以减少类与类之间的耦合性。
设计的时候需要先从下层开始思考,将下层模块抽象化,在考虑高层模块
Instance
public class ZhangSan {
public void BuyJavaBook(){
System.out.println("张三购买了java书");
}
public void BuyPythonBook(){
System.out.println("张三购买了python书");
}
}
public static void main(String[] args) {
ZhangSan zhangSan = new ZhangSan();
zhangSan.BuyJavaBook();
zhangSan.BuyPythonBook();
}
但此时如果张三还想要买购买别的书,比如C++的书籍,需要业务扩展,需要从低层到高层一次修改代码,需要再ZhangSan类中增加BuyCppBook() 方法,如此一来系统发布后非常不稳定,也不利于维护。
此时,就需要将所有的书籍定义一个规范,也就接口,所有的书籍都实现它,再通过Java的多态,可以输入不同的实现类
public interface Books {
void BuyBook();
}
public class JavaBook implements Books{
@Override
public void BuyBook() {
System.out.println("购买了Java书籍");
}
}
public class PythonBook implements Books{
@Override
public void BuyBook() {
System.out.println("购买了Java书籍");
}
}
public class ZhangSan {
public void Buy(Books book){
System.out.println("张三");
book.BuyBook();
}
public static void main(String[] args) {
ZhangSan zhangSan = new ZhangSan();
zhangSan.Buy(new JavaBook());
zhangSan.Buy(new PythonBook());
}
}
此时,如果需要拓展业务,只需要创建新的Books的实现类就可以了,这也是接口的好处
延申
实际上,这种方式就是依赖注入,之后会具体展开 还有别的两种注入方式就是通过 构造器(Constructor)和Setter方式
1.3 单一职责原则(Simple Responsibility Principle)
不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。
假设我们有一个类负责两个职责,一旦发生需求的变更,修改其中一个职责的逻辑代码,有可能导致另一个职责的功能发生故障。单一职责原则就是将两个职责用两个不同的类来实现,进行解耦
Instance
用课程举例,课程有直播课和视频课,直播课不能快进,视频课可以反复观看,功能职责不一样。
我们可以这样实现
public class Course {
public void study(String courseName){
if ("直播课".equals(courseName)){
System.out.println("正在学习" + courseName + "不能快进");
}else if ("视频课".equals(courseName)){
System.out.println("正在学习" + courseName + "可以反复观看");
}
}
}
public static void main(String[] args) {
Course course = new Course();
course.study("直播课");
course.study("视频课");
}
但是这样一来,Course类承担了两种处理逻辑。假设现在我们要对课程进行加密,但是直播课和视频课的加密逻辑不一样,就必须要修改代码,而修改代码的逻辑必会相互影响。
此时,单一职责原则就是将两个功能用两个类来实现。
public interface ICourse {
void study(String courseName);
}
public class LiveCourse implements ICourse{
@Override
public void study(String courseName) {
System.out.println("正在学习" + courseName + "不能快进");
}
}
public class VideoCourse implements ICourse{
@Override
public void study(String courseName) {
System.out.println("正在学习" + courseName + "可以反复观看");
}
}
public static void main(String[] args) {
ICourse course = new VideoCourse();
course.study("直播课");
course = new LiveCourse();
course.study("视频课");
}
此时,这两个职责就进行了解耦,后期对不同的职责进行扩展时就可以根据不同职责的特点针对扩展
1.4 接口隔离原则 (Interface Segregation Principle)
接口隔离原则是指使用多个专门的接口,而不是使用单一的总接口,客服端不应该依赖它不需要的接口或方法。
- 一个类对另一个类的依赖应建立在最小接口之上
- 建立单一接口,不要建立庞大臃肿的接口。
- 细化接口,接口中的方法尽量少
接口隔离原则符合我们常说的 高内聚、低耦合的思想,可以使类具有很好的可读性、可扩展性和可维护性。
Instance
现在我们将会对动物的一些行为进行描述
public interface Animal {
void eat();
void fly();
void swim();
}
public class Bird implements Animal{
@Override
public void eat() {}
@Override
public void fly() {}
@Override
public void swim() {}
}
public class Dog implements Animal{
@Override
public void eat() {}
@Override
public void fly() {}
@Override
public void swim() {}
}
/**
可以看出 Bird的 swim()只能空着,Dog的Fly()方法显然使不可能的,这时候就需要我们针对不同动物的行为来设计不同的接口
*/
public interface EatAnimal {
void eat();
}
public interface FlyAnimal {
void fly();
}
public interface SwimAnimal {
void swim();
}
public class Dog implements EatAnimal, SwimAnimal{
@Override
public void eat() {}
@Override
public void swim(){}
}
定义了三种不同的接口,该动物会有哪些行为就实现哪些接口,这样就非常清晰了
1.5 迪米特原则(Law of Demeter)
迪米特原则是指一个对象应该对其他对象保持最小的了解。主要强调:之和朋友交流,不和陌生人讲话。通俗地讲,一个类应该对自己需要耦合或调用的类知道得最少,你(被调用或耦合的类)的内部是如何地复杂与我无关,那是你的事情,我就知道你提供了这么多的public方法,我就调用这么多,其他我一概不关心。
Instance
现在来设计一个权限系统,Boss需要查看目前发布到线上的课程数量,这时候Boss 需要找到TeamLeader来进行统计,TeamLeader再把统计结果告诉Boss。
public class Course {
}
public class TeamLeader {
public void checkNumberOfCourses(List<Course> courseList){
System.out.println("目前已发布的课程数量:"+courseList.size());
}
}
public class Boss {
public void commandCheckNumber(TeamLeader teamLeader){
List<Course> courseList = new ArrayList<>();
//此时模拟Boss在一页一页往下翻名单,TeamLeader做实时统计
for (int i = 0; i < 20; i++) courseList.add(new Course());
teamLeader.checkNumberOfCourses(courseList);
}
}
public static void main(String[] args) {
Boss boss = new Boss();
TeamLeader teamLeader = new TeamLeader();
boss.commandCheckNumber(teamLeader);
}
到此,其功能都已经实现了。但是Boss不需要关心具体的Course,也不需要关心TeamLeader到底是如何统计的,Boss只需要最终的一个数字。
修改后
public class TeamLeader {
public void checkNumberOfCourses(){
List<Course> courseList = new ArrayList<>();
//此时模拟Boss在一页一页往下翻名单,TeamLeader做实时统计
for (int i = 0; i < 20; i++) courseList.add(new Course());
System.out.println("目前已发布的课程数量:"+courseList.size());
}
}
public class Boss {
public void commandCheckNumber(TeamLeader teamLeader){
teamLeader.checkNumberOfCourses();
}
}
此时,Boss和Course已经没有关系了。简单的来说,迪米特原则就是尽量将自己不关心的方法放在别的类中做,减少与自己的耦合
1.6 里氏替换原则 (Liskov Substitution Principle)
定义:如果对每一个类型为T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
约束
(1)子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
(2)子类中可以增加自己特有的方法。
(3)当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
(4)当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
通俗的讲,就是把父类的对象改成子类的对象后,运行结果需要一致。也就是说子类不可以重写父类的方法,如果要进行业务扩展,需要自己增加方法。
Instance
在开闭原则例子中,修改代码后,虽然我们遵循了开闭原则,但还是违背了里氏替换原则。
public class JavaDiscountBook extends JavaBook{
public JavaDiscountBook(Integer ISBN, String name, Double price) {
super(ISBN, name, price);
}
@Override
public Double getPrice() {
return super.getPrice() * 0.8;
}
}
在JavaDiscountBook中,不能重写父类的方法。如果在业务中,讲父类替换为子类,得到的价格就是原来的0.8倍,这与与拿来的价格不同违背了里氏替换原则。
public class JavaDiscountBook extends JavaBook{
public JavaDiscountBook(Integer ISBN, String name, Double price) {
super(ISBN, name, price);
}
public Double getDiscountPrice() {
return super.getPrice() * 0.8;
}
}
更好的写法就是就是增加新的方法,不修改父类的方法
2.1 Reference
主要材料:《Spring 5核心原理与30个类手写实战》
开闭原则:https://blog.csdn.net/sinat_20645961/article/details/48239347
依赖倒置原则:https://www.jianshu.com/p/c3ce6762257c
单一职责原则:https://blog.csdn.net/zhengzhb/article/details/7278174
接口隔离原则:https://blog.csdn.net/king123456man/article/details/81626059
迪米特原则:https://blog.csdn.net/weixin_33984032/article/details/92439242
里氏替换原则:https://blog.csdn.net/zhonghuan1992/article/details/37461407?utm_medium=distribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0.no_search_link&spm=1001.2101.3001.4242