1.1开闭原则

  • 开闭原则(open-closed principle,OCP)是指一个软件实体(如类,模块和函数)应该对扩展开放,对修改关闭。所谓的开闭,也正是对扩展和修改两个行为的一个原则。
  • 强调用抽象构建框架,用实现扩展细节,可以提高软件系统的可复用性及可维护性;例如版本更新时,我们尽可能不修改源代码就可以添加新功能。
    首先创建一个课程接口JavaCourse
/**
* TODO 开闭原则——课程接口
* @author ss_419
*/
public interface ICourse {
Integer getId();
String getName();
Double getPrice();
}

整个课程有许多分类,Java、Python、Js等等…
紧接着我们创建一个Java课程类,该类实现课程接口

/**
* TODO Java课程类
*
* @author ss_419
* @version 1.0
* @date 2023/1/29 10:37
*/
public class JavaCourse implements ICourse{
private Integer id;
private String name;
private Double price; public JavaCourse(Integer id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
} @Override
public Integer getId() {
return this.id;
} @Override
public String getName() {
return this.name;
} @Override
public Double getPrice() {
return this.price;
}
}

突然有一天,老板说把Java课程的价格做一下优惠,但是如果修改JavaCourse中的getPrice()方法,则存在一定的风险,可能影响其他地方的调用结果,为了避免此类事件发生 ,我们就需要在不修改原有代码的前提下,实现这个价格优惠的功能

  • 解决方案:创建一个处理优惠逻辑的类JavaDiscountCourse
/**
* TODO 开闭原则——JavaCourse降价活动
*
* @author ss_419
* @version 1.0
* @date 2023/1/29 10:38
*/
public class JavaDiscountCourse extends JavaCourse{
public JavaDiscountCourse(Integer id, String name, Double price) {
super(id, name, price);
} /**
* 出现降价活动不能够直接去修改JavaCourse中的getPrice()方法,所以通过继承JavaCourse类,来返回原商品降价之后的价格
*/
/**
* 获取商品原价格
* @return
*/
public Double getOriginPrice() {
System.out.println("原商品价格");
return super.getPrice();
} public Double getPrice(){
System.out.println("降价后的商品价格");
return super.getPrice() * 0.618;
} }

1.2依赖倒置原则

  • 依赖倒置原则(Dependence Inversion Principle,DIP)是指设计代码结构时,高层模块不应该依赖低层模块,二者都应该依赖其抽象。
  • 抽象不应该依赖与细节,细节应该依赖抽象。
  • 通过依赖倒置,可以减少类与类之间的耦合性,提高系统的稳定性,提高代码的可读性和可维护性,并且能降低修改程序所造成的风险。

创建一个Tom类:

/**
* TODO 依赖倒置——初始状态
*
* @author ss_419
* @version 1.0
* @date 2023/1/29 11:42
*/
public class Tom {
public void studyJavaCourse(){
System.out.println("studyJavaCourse");
}
public void studyPythonCourse(){
System.out.println("studyPythonCourse studyPythonCourse");
} public static void main(String[] args) {
Tom tom = new Tom();
tom.studyJavaCourse();
tom.studyPythonCourse();
}
}

目前Tom是在学习Java和Python,但是后续想学习其他课程的时候,就需要添加一个方法,没多学习一个就得多添加一个方法,这个时候,因为业务扩展,要从低层到高层(调用层)依次修改代码,这样极其不方便。

  • 因此,我们接下来做一些优化:
  1. 创建一个课程接口ICourse:
/**
* TODO 依赖倒置
* @author ss_419
*/
public interface ICourse {
void study();
}
  1. 编写JavaCourse类:
/**
* TODO
*
* @author ss_419
* @version 1.0
* @date 2023/1/29 12:10
*/
public class JavaCourse implements ICourse{
@Override
public void study() {
System.out.println("Java课程.....");
}
}
  1. 编写PythonCourse类:
/**
* TODO
*
* @author ss_419
* @version 1.0
* @date 2023/1/29 12:12
*/
public class PythonCourse implements ICourse{
@Override
public void study() { System.out.println("Python课程学习......");
}
}
  1. 修改Tom类:
public class Tom {
/**
* DIP优化版本
* @param course
*/
public void study(ICourse course){
course.study();
}
}
  1. 新的调用方式:
public static void main(String[] args) {
Tom tom = new Tom();
// 新的调用方式
tom.study(new JavaCourse());
tom.study(new JavaCourse());
tom.study(new PythonCourse());
// tom.studyJavaCourse();
// tom.studyPythonCourse();
}
  1. DIP构造器注入方式
    在使用构造器注入时,在调用的时候,每次都要创建实例
public class Tom {
private ICourse course; /**
* DIP——构造器注入方式
* @param course
*/
public Tom(ICourse course) {
this.course = course;
}
public void study(){
course.study();
}
}

调用方式:

    public static void main(String[] args) {
Tom tom = new Tom(new JavaCourse());
tom.study();
}
  1. 当Tom时全局单例,只能选择用Setter方式来注入
public class Tom {
private ICourse course; /**
* 使用setter方式来注入
* @param course
*/
public void setCourse(ICourse course) {
this.course = course;
}
public void study(){
course.study();
}
}

调用代码:

 public static void main(String[] args) {
Tom tom = new Tom();
tom.setCourse(new JavaCourse());
tom.study();
tom.setCourse(new PythonCourse());
tom.study();
}

单一职责原则

  • 单一职责(Simple Responsibility Pinciple,SRP)是指不要存在多于一个导致类变更的原因
  • 假设我们有一个类负责两个职责,一旦发生需求变更,修改其中一个职责的逻辑代码,有可能导致另一个职责的功能发生故障,这样一来,这个类就存在两个导致类变更的原因。
  • 为了避免上述情况的出现,我们将两个职责用两个类来实现,进行解耦。后期需求变更维护互不影响。
  • 总体来说,就是一个类、接口或方法只负责一项职责

课程有直播课和录播课两类;直播课不能快进和快退,录播课可以任意地反复观看,功能职责不一样。
首先创建一个Course类:

/**
* TODO 单一职责Simple Responsibility Pinciple
* @author ss_419
*/
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承担了两种处理逻辑。后续如果对其中一种课程进行加密,由于两种课程的加密逻辑一定不相同,必须修改代码。而修改代码的逻辑势必会相互影响,容易带来不可控的风险。

  • 接下来我们对职责进行解耦
  1. 首先创建两个类LiveCourse和ReplayCourse
/**
* TODO SRP优化版
*
* @author ss_419
* @version 1.0
* @date 2023/1/29 14:47
*/
public class LiveCourse {
public void study(String courseName){
System.out.println("我是直播课程==>" + courseName);
}
}
/**
* TODO
*
* @author ss_419
* @version 1.0
* @date 2023/1/29 14:49
*/
public class ReplayCourse {
public void study(String courseName){ System.out.println("我是录播课程==>"+courseName);
}
}
  1. 调用过程
    public static void main(String[] args) {
LiveCourse liveCourse = new LiveCourse();
liveCourse.study("直播课");
ReplayCourse recordCourse = new ReplayCourse();
recordCourse.study("录播课");
}

通过创建两个单独的类,就实现了将两个职责从原先的Course中拆分的操作

接口隔离原则

接口隔离原则(Interface Segregation Pinciple,ISP)是指用多个专门的接口,而不使用单一的总接口,客户端不应该依赖它不需要的接口。
符合高内聚、低耦合的设计思想

  • 一个类对另一个类的依赖应该建立在最小的接口之上。
  • 建立单一接口,不要监理庞大臃肿的接口。
  • 尽量细化接口,接口中的方法尽量少(适度即可)。

1创建一个IAnimal接口:

/**
* TODO 接口隔离原则
* @author ss_419
*/
public interface IAnimal {
void eat();
void fly();
void swim();
}`在这里插入代码片`

2创建Bird类:

/**
* TODO
*
* @author ss_419
* @version 1.0
* @date 2023/1/29 15:05
*/
public class Bird implements IAnimal{
@Override
public void eat() {
System.out.println("bird eat");
} @Override
public void fly() {
System.out.println("bird fly");
} @Override
public void swim() { }
}

3创建Dog类:

/**
* TODO
*
* @author ss_419
* @version 1.0
* @date 2023/1/29 15:11
*/
public class Dog implements IAnimal{
@Override
public void eat() {
System.out.println("I am a dog 吧唧吧唧");
} @Override
public void fly() { } @Override
public void swim() {
System.out.println("Dog swim....");
}
}

通过2、3可以看出,Bird的swim方法只能空着(因为鸟不能游泳。。。。),而且Dog的fly方法也是实现不了的。
这个时候我们针对不同的动物行为来设计不同的接口,分别设计IEatAnimal、IFlyAnimal、ISwimAnimal接口:

/**
* TODO
*
* @author ss_419
* @version 1.0
* @date 2023/1/29 15:16
*/
public interface IEatAnimal {
void eat();
}
/**
* @author ss_419
*/
public interface IFlyAnimal {
void fly();
}
/**
* @author ss_419
*/
public interface ISwimAnimal {
void swim();
}

实现上面三个接口之后,修改dog类如下:

/**
* TODO
*
* @author ss_419
* @version 1.0
* @date 2023/1/29 15:11
*/
public class Dog implements IEatAnimal, ISwimAnimal { @Override
public void eat() {
System.out.println("dog --> eat");
} @Override
public void swim() {
System.out.println("dog --> swim");
}
}

这样一来就不会存在实现类中不需要的方法了,防止客户端使用了它不需要的接口。

迪米特原则

迪米特原则(Law Of Demeter LoD)是指一个对象应该对其他对象保持最少的了解,又叫最少知道原则(Least Knowledge Pinciple,LKP),尽量降低类与类之间的耦合度。

  • 只和朋友交流,不和陌生人说话
    1、首先创建一个Course类
public class Course {
}

2、创建一个TeamLeader,用于统计Course信息

public class TeamLeader {
public void checkNumberOfCourses(){
List<Course> courseList = new ArrayList<>();
for (int i = 0; i < 20; i++) {
courseList.add(new Course());
}
System.out.println("目前已发布的课程数量是:"+courseList.size());
}
}

3、创建一个Boss,老板想看课程数量:

public class Boss {
public void commandCheckNumber(TeamLeader teamLeader){
// 通过TeamLeader来查询Course信息
teamLeader.checkNumberOfCourses();
}
}

4、调用方法:

 public static void main(String[] args) {
Boss boss = new Boss();
// 这样一来,Boss就和Course分离开来了,boss不用去关心Course的具体实现,他只想知道有多少个课程数量
boss.commandCheckNumber(new TeamLeader());
}

这样一来,Boss就和Course分离开来了,boss不用去关心Course的具体实现,他只想知道有多少个课程数量

里氏替换原则

里氏替换原则(Liskov Substitution Princple,LSP)是指如果对每一个类型为T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有的对象O1都替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。

  • 一个软件实体如果适用于一个父类,那么一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。
  • 子类可以扩展父类的功能,但不能改变父类原有的功能
  1. 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
  2. 子类可以增加自己持有的方法
  3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松
  4. 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或与父类一样。(上转型或者转同型)

【Spring 5核心原理】1设计模式的更多相关文章

  1. Spring Boot核心原理

    Spring Boot核心原理 spring-boot-starter-xxx  方便开发和配置 1.没有depoy setup tomcat 2.xml文件里面的没有没有了 @SpringBootA ...

  2. spring IOC核心原理

    下面来了解一下Spring到底是怎么运行的. public static void main(String[] args) { ApplicationContext context = new Fil ...

  3. 手写一个最简单的IOC容器,从而了解spring的核心原理

    从事开发工作多年,spring源码没有特意去看过.但是相关技术原理倒是背了不少,毕竟面试的那关还是得过啊! 正所谓面试造火箭,工作拧螺丝.下面实现一个最简单的ioc容器,供大家参考. 1.最终结果 2 ...

  4. 30个类手写Spring核心原理之AOP代码织入(5)

    本文节选自<Spring 5核心原理> 前面我们已经完成了Spring IoC.DI.MVC三大核心模块的功能,并保证了功能可用.接下来要完成Spring的另一个核心模块-AOP,这也是最 ...

  5. Spring核心原理分析之MVC九大组件(1)

    本文节选自<Spring 5核心原理> 1 什么是Spring MVC Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 S ...

  6. 30个类手写Spring核心原理之环境准备(1)

    本文节选自<Spring 5核心原理> 1 IDEA集成Lombok插件 1.1 安装插件 IntelliJ IDEA是一款非常优秀的集成开发工具,功能强大,而且插件众多.Lombok是开 ...

  7. 30个类手写Spring核心原理之Ioc顶层架构设计(2)

    本文节选自<Spring 5核心原理> 1 Annotation(自定义配置)模块 Annotation的代码实现我们还是沿用Mini版本的,保持不变,复制过来便可. 1.1 @GPSer ...

  8. 30个类手写Spring核心原理之依赖注入功能(3)

    本文节选自<Spring 5核心原理> 在之前的源码分析中我们已经了解到,依赖注入(DI)的入口是getBean()方法,前面的IoC手写部分基本流程已通.先在GPApplicationC ...

  9. 30个类手写Spring核心原理之MVC映射功能(4)

    本文节选自<Spring 5核心原理> 接下来我们来完成MVC模块的功能,应该不需要再做说明.Spring MVC的入口就是从DispatcherServlet开始的,而前面的章节中已完成 ...

  10. 30个类手写Spring核心原理之自定义ORM(上)(6)

    本文节选自<Spring 5核心原理> 1 实现思路概述 1.1 从ResultSet说起 说到ResultSet,有Java开发经验的"小伙伴"自然最熟悉不过了,不过 ...

随机推荐

  1. ElasticSearch之cat component templates API

    命令样例如下: curl -X GET "https://localhost:9200/_cat/component_templates?v=true&pretty" -- ...

  2. 变更(重命名)AD域名称

    文章来自:https://blog.51cto.com/sxleilong/1377731 示例将域名contoso.com重命名为Corp.sxleilong.com 查看当前fsmo的5个角色. ...

  3. 微软用它取代了`Nginx`吞吐量提升了百分之八十!

    Azure应用服务用YARP取代了Nginx,获得了80%以上的吞吐量.他们每天处理160B多个请求(1.9 m RPS).这是微软的一项了不起的技术创新. 首先我们来介绍一下什么是Yarp Yarp ...

  4. WinForm如何将子控件插入FlowLayoutPanel开始位置

    需求描述 动态将控件插入到FlowLayoutPanel控件的开始位置 实现方案 将控件添加到FlowLayoutPanel的Controls集合中,默认插到末尾 使用SetChildIndex方法更 ...

  5. osgEarth使用笔记2——推荐两个底图数据

    目录 1. 概述 2. 详论 2.1. Blue Marble 2.2. Bright Earth eAtlas Basemap 3. 分享 1. 概述 可以通过osgEarth自带的world.ti ...

  6. 电商业务容器化遇瓶颈,公有云Docker镜像P2P加速很安全

    当前,电商平台会采用基于Docker的容器技术来承载618大促期间的一些关键业务版块,包括最简单的商品图片展示.订单详情页面等等. 通过容器化改造,电商平台的每个业务版块解耦,可以独立开发.部署和上线 ...

  7. CVE-2022-22965 漏洞分析,安全问题早发现

    摘要:Spring在进行参数绑定时调用的 BeanWrapperImpl在进行JavaBean操作时触发了此漏洞. 本文分享自华为云社区<CVE-2022-22965 漏洞分析>,作者:X ...

  8. Git 工具 - 子模块: submodule与subtree的使用

    git日常使用中,基本都是一个项目一个Git仓库的形式,那么当我们的代码中碰到了业务级别的需要复用的代码,我们一般怎么做呢? 比如:某个工作中的项目需要包含并使用另一个项目. 也许是第三方库,或者你独 ...

  9. A/B测试助力游戏业务增长

    更多技术交流.求职机会.试用福利,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 中国游戏行业发展现状及挑战 国内市场增长乏力 2021年游戏销售收入2965.13亿元,同比增长6.4% ...

  10. API 设计最佳实践(简版)

    Restful API 本文简称API,是一个种面向资源的架构.在Restful中一个API对应一个资源,资源可以是文本,图片,视频等.API特征有如下: 每一个URI代表一种资源 客户端和服务器之间 ...