【Spring 5核心原理】1设计模式
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,但是后续想学习其他课程的时候,就需要添加一个方法,没多学习一个就得多添加一个方法,这个时候,因为业务扩展,要从低层到高层(调用层)依次修改代码,这样极其不方便。
- 因此,我们接下来做一些优化:
- 创建一个课程接口ICourse:
/**
* TODO 依赖倒置
* @author ss_419
*/
public interface ICourse {
void study();
}
- 编写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课程.....");
}
}
- 编写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课程学习......");
}
}
- 修改Tom类:
public class Tom {
/**
* DIP优化版本
* @param course
*/
public void study(ICourse course){
course.study();
}
}
- 新的调用方式:
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();
}
- 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();
}
- 当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承担了两种处理逻辑。后续如果对其中一种课程进行加密,由于两种课程的加密逻辑一定不相同,必须修改代码。而修改代码的逻辑势必会相互影响,容易带来不可控的风险。
- 接下来我们对职责进行解耦
- 首先创建两个类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);
}
}
- 调用过程
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的子类型。
- 一个软件实体如果适用于一个父类,那么一定适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。
- 子类可以扩展父类的功能,但不能改变父类原有的功能
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法
- 子类可以增加自己持有的方法
- 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入/入参)要比父类方法的输入参数更宽松
- 当子类的方法实现父类的方法时(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类更严格或与父类一样。(上转型或者转同型)
【Spring 5核心原理】1设计模式的更多相关文章
- Spring Boot核心原理
Spring Boot核心原理 spring-boot-starter-xxx 方便开发和配置 1.没有depoy setup tomcat 2.xml文件里面的没有没有了 @SpringBootA ...
- spring IOC核心原理
下面来了解一下Spring到底是怎么运行的. public static void main(String[] args) { ApplicationContext context = new Fil ...
- 手写一个最简单的IOC容器,从而了解spring的核心原理
从事开发工作多年,spring源码没有特意去看过.但是相关技术原理倒是背了不少,毕竟面试的那关还是得过啊! 正所谓面试造火箭,工作拧螺丝.下面实现一个最简单的ioc容器,供大家参考. 1.最终结果 2 ...
- 30个类手写Spring核心原理之AOP代码织入(5)
本文节选自<Spring 5核心原理> 前面我们已经完成了Spring IoC.DI.MVC三大核心模块的功能,并保证了功能可用.接下来要完成Spring的另一个核心模块-AOP,这也是最 ...
- Spring核心原理分析之MVC九大组件(1)
本文节选自<Spring 5核心原理> 1 什么是Spring MVC Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 S ...
- 30个类手写Spring核心原理之环境准备(1)
本文节选自<Spring 5核心原理> 1 IDEA集成Lombok插件 1.1 安装插件 IntelliJ IDEA是一款非常优秀的集成开发工具,功能强大,而且插件众多.Lombok是开 ...
- 30个类手写Spring核心原理之Ioc顶层架构设计(2)
本文节选自<Spring 5核心原理> 1 Annotation(自定义配置)模块 Annotation的代码实现我们还是沿用Mini版本的,保持不变,复制过来便可. 1.1 @GPSer ...
- 30个类手写Spring核心原理之依赖注入功能(3)
本文节选自<Spring 5核心原理> 在之前的源码分析中我们已经了解到,依赖注入(DI)的入口是getBean()方法,前面的IoC手写部分基本流程已通.先在GPApplicationC ...
- 30个类手写Spring核心原理之MVC映射功能(4)
本文节选自<Spring 5核心原理> 接下来我们来完成MVC模块的功能,应该不需要再做说明.Spring MVC的入口就是从DispatcherServlet开始的,而前面的章节中已完成 ...
- 30个类手写Spring核心原理之自定义ORM(上)(6)
本文节选自<Spring 5核心原理> 1 实现思路概述 1.1 从ResultSet说起 说到ResultSet,有Java开发经验的"小伙伴"自然最熟悉不过了,不过 ...
随机推荐
- ElasticSearch之cat component templates API
命令样例如下: curl -X GET "https://localhost:9200/_cat/component_templates?v=true&pretty" -- ...
- 变更(重命名)AD域名称
文章来自:https://blog.51cto.com/sxleilong/1377731 示例将域名contoso.com重命名为Corp.sxleilong.com 查看当前fsmo的5个角色. ...
- 微软用它取代了`Nginx`吞吐量提升了百分之八十!
Azure应用服务用YARP取代了Nginx,获得了80%以上的吞吐量.他们每天处理160B多个请求(1.9 m RPS).这是微软的一项了不起的技术创新. 首先我们来介绍一下什么是Yarp Yarp ...
- WinForm如何将子控件插入FlowLayoutPanel开始位置
需求描述 动态将控件插入到FlowLayoutPanel控件的开始位置 实现方案 将控件添加到FlowLayoutPanel的Controls集合中,默认插到末尾 使用SetChildIndex方法更 ...
- osgEarth使用笔记2——推荐两个底图数据
目录 1. 概述 2. 详论 2.1. Blue Marble 2.2. Bright Earth eAtlas Basemap 3. 分享 1. 概述 可以通过osgEarth自带的world.ti ...
- 电商业务容器化遇瓶颈,公有云Docker镜像P2P加速很安全
当前,电商平台会采用基于Docker的容器技术来承载618大促期间的一些关键业务版块,包括最简单的商品图片展示.订单详情页面等等. 通过容器化改造,电商平台的每个业务版块解耦,可以独立开发.部署和上线 ...
- CVE-2022-22965 漏洞分析,安全问题早发现
摘要:Spring在进行参数绑定时调用的 BeanWrapperImpl在进行JavaBean操作时触发了此漏洞. 本文分享自华为云社区<CVE-2022-22965 漏洞分析>,作者:X ...
- Git 工具 - 子模块: submodule与subtree的使用
git日常使用中,基本都是一个项目一个Git仓库的形式,那么当我们的代码中碰到了业务级别的需要复用的代码,我们一般怎么做呢? 比如:某个工作中的项目需要包含并使用另一个项目. 也许是第三方库,或者你独 ...
- A/B测试助力游戏业务增长
更多技术交流.求职机会.试用福利,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 中国游戏行业发展现状及挑战 国内市场增长乏力 2021年游戏销售收入2965.13亿元,同比增长6.4% ...
- API 设计最佳实践(简版)
Restful API 本文简称API,是一个种面向资源的架构.在Restful中一个API对应一个资源,资源可以是文本,图片,视频等.API特征有如下: 每一个URI代表一种资源 客户端和服务器之间 ...