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-设计模式的更多相关文章

  1. Spring 5 , 狭义上的spring 以及 广义上的spring , spring 设计模式

    Spring 5 距离 Spring4 发布有4年多, 所以说Spring 5是一个重要的版本 要构建和运行Spring 5应用程序, 至少需要Java EE 7 和JDK 8 , 之前的JDK和Ja ...

  2. spring设计模式_代理模式

    代理模式应该是Spring核心设计模式之一了 先说下代理模式特性: 1.有代理人和被代理人 2.对于被代理的人来说,这件事情是一定要做的,但是我又不想做,所有就找代理人来做. 3.需要获取到被代理人的 ...

  3. Spring设计模式_策略模式/其他

    策略模式特性 1.执行最终结果一样 2.执行过程和执行逻辑不一样 3.使用同一接口 达到目的就可以了 Git地址 https://github.com/wujiachengSH/WjcStrategy ...

  4. Spring 设计模式之责任链模式

    [应用] 以下是一段代码,Spring MVC 的 diapatcherServlet 的 doDispatch 方法中,获取与请求匹配的处理器(HandlerExecutionChain) getH ...

  5. 两张导图带你走进Spring设计模式与编程思想

    两张思维导图带你了解Spring Spring常用设计模式 Spring设计思想

  6. [spring] -- 设计模式篇

    工厂模式 Spring使用工厂模式可以通过 BeanFactory 或 ApplicationContext 创建 bean 对象. BeanFactory :延迟注入(使用到某个 bean 的时候才 ...

  7. Spring 设计模式介绍

    JDK 中用到了那些设计模式?Spring 中用到了那些设计模式?这两个问题,在面试中比较常见.我在网上搜索了一下关于 Spring 中设计模式的讲解几乎都是千篇一律,而且大部分都年代久远.所以,花了 ...

  8. spring设计模式之applicationContext.getBean("beanName")思想

    1.背景 在实际开发中我们会经常遇到不同的业务类型对应不同的业务处理,而这个业务类型又是经常变动的; 比如说,我们在做支付业务的时候,可能刚开始需要实现支付宝支付和微信支付,那么代码逻辑可能如下 /* ...

  9. Spring设计模式_工厂模式

    先说下工厂模式的特性 1.对于调用者来说,影藏了复杂的逻辑处理过程,调用者只关心执行结果. 2.工厂要对结果负责,保证生产出符合规范的产品. Git代码地址  https://github.com/w ...

  10. java面试题:Spring

    Spring 面试时,最好能结合底层代码说出IOC,AOP或Spring MVC的流程,能说出拦截器的底层. 如果看过Spring的源码,并能结合设计模式表达,是很大的加分项. IOC Q:讲一下IO ...

随机推荐

  1. 阿里开源的几个中间件 dubbo/RocketMQ/canal/druid 代码还是很不错的

    阿里开源的几个中间件 dubbo/RocketMQ/canal/druid 代码还是很不错的

  2. NavicatPremium16破解!!!!!亲测可用!!!!!!!!!!!!!!!!!

    前言 Navicat premium是一款数据库管理工具,是一个可多重连线资料库的管理工具,它可以让你以单一程式同时连线到 MySQL.SQLite.Oracle 及 PostgreSQL 资料库,让 ...

  3. vs2015当前不会命中断点,还没有为该文档

    经百度,需在项目>项目属性>生成>优化代码的勾去掉>保存>再次F11调试可解决

  4. java发送短信验证码带倒计时

    分享一个完整的java发送短信验证码的完整实例,这是一个官方的使用demo,带有60秒倒计时功能. 效果: 我使用的是榛子云短信平台, 官网地址:http://smsow.zhenzikj.com 我 ...

  5. 作业三:CART回归树

    作业三:CART回归树 20大数据三班 博客链接 学号 201613336 问题一: 表1为拖欠贷款人员训练样本数据集,使用CART算法基于该表数据构造决策树模型,并使用表2中测试样本集确定剪枝后的最 ...

  6. 关于*p++的执行顺序

    不确定*p++哪个优先级高了,想偷懒到百度找找解释,发现高赞的评论下也骂声一片,还是回头自己试试. 1 #include <iostream> 2 using namespace std; ...

  7. AX2012 data() 和 buf2buf()的区别

    data() 和 buf2buf()都是AX2012 里面可以选择使用的数据拷贝函数.不同的是data会拷贝原始记录里面的所有字段,包括系统字段(公司,创建人等).而buf2buf在拷贝数据时则不会拷 ...

  8. Spring5框架

    Spring5框架 一.Spring框架概述 1.1 Spring框架简介 Spring是一个开源框架,它由Rod Johnson创建.它是为了解决企业应用开发的复杂性而创建的.Spring使用基本的 ...

  9. opencv3 7.3 重映射 仿射变换

    重映射的概念 将一幅图像某位置的像素放置到另外一幅图像的指定位置上,需要对非整数像素坐标重映射来表达每个像素的新位置. g(x,y)=f(h(x,y)); 实现重映射 remap()函数 dst(x, ...

  10. Kubernetes快速部署

    Kubernetes快速部署 kubernetes简介 kubernetes,是一个全新的基于容器技术的分布式架构领先方案,是谷歌严格保密十几年的秘密武器----Borg系统的一个开源版本,于2014 ...