基于LSP原则对方法的一点相关

2024-03-15 04:30
文章标签 方法 相关 原则 一点 lsp

本文主要是介绍基于LSP原则对方法的一点相关,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

方法

  方法,或者叫类的行为,定义了类能够实现的功能。在任何程序设计语言中,方法一个重要的特性就是名字的运用。我们创建一个对象时,会分配到一个保存区域的名字。方法名代表的是一种具体的行动。通过名字描述自己的系统,可使自己的程序更易人们理解和修改。它就像写散文——目的是与读者沟通。

构造方法

  构造方法是与一种与类同名且无返回值类型的方法。构造方法在实例化一个对象的同时进行编译,它分为无参构造和含参构造。无参构造顾名思义就是不含参数类型和参数的方法。

Static关键字

方法的实现

  通常在开发中,我们会定义一些接口或者抽象类,然后编码实现。在实现这些接口或者抽象类时,会进行方法的重载(overload)和覆写(override)。而重载和覆写区别在就在于重载是方法名相同,参数类型和参数个数不同;覆写是方法名和参数类型以及个数与父类方法完全相同。

子类必须完全实现父类的方法

  根据LSP原则,在此定义一个汽车类图

  汽车能够启动,而在车主类中定义一个方法racing,进行飙车,具体用什么车来飙车,调用车型的时候才知道,AbstractCar的代码如下:

1 public abstract class AbstratCar {
2 
3     //汽车能够启动
4     public abstract void drive();
5     
6 }

  跑车的实现类

1 public class SportsCar extends AbstratCar{
2     //跑车速度快
3     @Override
4     public void drive() {
5         // TODO Auto-generated method stub
6         System.out.println("跑车启动");
7     }
8 
9 }

  拖拉机的实现类

1 public class Tractor extends AbstratCar{
2     //拖拉机跑起来冒烟
3     @Override
4     public void drive() {
5         // TODO Auto-generated method stub
6         System.out.println("拖拉机启动");
7     }
8 
9 }

  卡车的实现类

1 public class Truck extends AbstratCar{
2     //卡车能拉货
3     @Override
4     public void drive() {
5         // TODO Auto-generated method stub
6         System.out.println("卡车启动");
7     }
8 
9 }

  司机的实现类

public class Owner {//定义司机的汽车private AbstracCar car;//给司机一张车public void setCar(AbstracCar _car){this.car = _car;}public void racing(){car.drive();System.out.println("老司机正在飙车");}
}

 

  汽车能够启动,但汽车是抽象的,具体是什么车启动需要在开车的时候(也就是场景中)才能通过drive方法确定,场景类Client实现入下:

 1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         //产生一个老司机
 5         Owner oldDriver = new Owner();
 6         //给老司机一张车
 7         oldDriver.setCar(new SportsCar());
 8         oldDriver.racing();
 9         
10     }
11 
12 }

  运行结果如下:

跑车启动
老司机正在飙车

  在这个程序中,我们给了老司机一张跑车,然后老司机就开始飙车了。如果老司机要开拖拉机,直接修改oldDriver.setCar(new SportsCar())为oldDriver.setCar(new (Tractor))即可,在编写程序时车主类根本不用知道是哪一个型号的车(子类)被传入。

  在类中调用其它类是务必要使用父类或接口,如果不能使用父类或接口,则说明类的设计已经违背LSP原则。

  那再来看一看当出现一张玩具车时的定义。修改后的类图如下:

  在这里有个问题,玩具车是不能用来飙车的,所以不能写在drive方法中。

  玩具车实现

1 public class ToyCar extends AbstratCar{
2     // 玩具车不能启动,但编译器要求实现这个方法,只能虚构一个
3     @Override
4     public void drive() {
5         // 玩具车不能启动,故此方法不实现
6         
7     }
8 
9 }

  由于引入了新的子类,场景类中也使用了该类,Client稍作修改,如下:

 1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         //产生一个老司机
 5         Owner oldDriver = new Owner();
 6         //给老司机一张车
 7         oldDriver.setCar(new ToyCar());
 8         oldDriver.racing();
 9         
10     }
11 
12 }

  运行结果如下

老司机正在飙车

  然而玩具车无法启动,因此不能够飙车,此时正常的业务逻辑无法继续运行,在此有两种解决办法:

    1.在Owner类中增加判断,如果是玩具车,就不能飙车。这可以解决问题,但是在程序中没增加一个类,所有与这个父类有关系的类就必须修改,因此这个方法行不通。

    2.ToyCar脱离继承,建立一个独立的父类,可以与AbastrctCar建立关联委托关系,如下图:

  在AbstractToy中奖部分数据的处理委托给AbstractCar中处理,然后两个基类下的子类自由延展,互不影响。

  按照Java三大特征,继承就是让子类拥有父类的方法和属性,然后可以重写父类的方法。按照继承原则,上面的玩具车继承AbstractCar是完全没有问题的,但是在具体的应用场景中需要考虑下面这问题:子类是否能够完整的实现父类的业务,否则就会想上面一样出现业务逻辑混乱的问题。

  如果子类不能完整的实现父类方法,或者父类的某些方法在子类中已经发生畸变,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。

子类可以有自己的个性

  子类能够有自己的方法和属性,在LSP原则下,子类出现的地方,父类未必能胜任。依然以上文的汽车为例,将法拉第和奔驰引入跑车的子类。

  奔驰继承SportsCar类,霸道总裁(Ceo)直接用奔驰进行飙车。

  奔驰车类实现

public class Benz extends SportsCar{//豪车上一般会带个妹子public void girl(){System.out.println("香车美人");}public void drive(){System.out.println("奔驰启动");}
}

  霸道总裁类实现

 1 public class Ceo {
 2     private Benz benz;
 3     
 4     public void setCar(Benz benz){
 5         this.benz = benz;
 6     }
 7     
 8     public void racing(){
 9         //上车前先带个妹子
10         benz.girl();
11         //开车开车
12         benz.drive();
13     }
14 }

 

  业务场景类实现

 1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         //产生一个老司机
 5         Ceo oldDriver = new Ceo();
 6         //给老司机一张车
 7         oldDriver.setCar(new Benz());
 8         oldDriver.racing();
 9         
10     }
11 
12 }

  运行结果如下

香车美人
奔驰启动

  在这里,系统直接调用了子类,也就是直接将子类传递进来,那么在这个时候能不能直接使用父类传递进来呢?修改一下代码如下:

 1 public class Client {
 2 
 3     public static void main(String[] args) {
 4         //产生一个老司机
 5         Ceo oldDriver = new Ceo();
 6         //给老司机一张车
 7         oldDriver.setCar((Benz)new SportsCar());
 8         oldDriver.racing();
 9         
10     }
11 
12 }

  运行结果如下:

Exception in thread "main" java.lang.ClassCastException: demo7.SportsCar cannot be cast to demo7.Benzat demo7.Client.main(Client.java:11)

  这个时候会抛出java.lang.ClassCastException异常,这也就是常说的,向下转型(downcast)是不安全的,也就是说,子类出现的地方父类未必可以出现。

覆盖或实现父类的方法时输入的参数可以被放大

  方法中的输入参数称为前置条件,在继承关系中前置条件范围的不同会造成什么关系呢,让我们测试一下。

1 public class Father {
2     public Collection doSomething(HashMap map){
3         System.out.println("父类被执行");
4         
5         return map.values();
6         
7     }
8 }

  定义父类代码

public class Son extends Father{public Collection doSomething(Map map){System.out.println("子类被执行");return map.values();}
}

  定义子类

 1 public class Client {
 2 
 3     public static void invoker(){
 4         //父类存在的地方,子类就应该能够存在
 5         Father f = new Father();
 6         HashMap map = new HashMap();
 7         f.doSomething(map);
 8     }
 9     
10     public static void main(String[] args) {
11         invoker();
12     }
13 
14 }

  定义场景类

父类被执行

  运行结果如上。根据LSP原则,父类存在的地方子类就应该能存在,如果将父类替换替换一下,会有什么结果呢。

 1 public class Client {
 2 
 3     public static void invoker(){
 4         //父类存在的地方,子类就应该能够存在
 5         Son f = new Son();
 6         HashMap map = new HashMap();
 7         f.doSomething(map);
 8     }
 9     
10     public static void main(String[] args) {
11         invoker();
12     }
13 
14 }

  运行结果如下

父类被执行

  两段运行结果都一样,这是因为父类方法输入的参数是HashMap类型,子类的输入类型参数是Map类型,也就是说子类输入参数类型的范围扩大了,子类代替父类传递到调用者中,子类的方法永远不会被执行。那如果修改一下将父类的前置条件扩大,会出现什么情况

1 public class Father {
2     public Collection doSomething(Map map){
3         System.out.println("父类被执行");
4         
5         return map.values();
6         
7     }
1 public class Son extends Father{
2     public Collection doSomething(HashMap map){
3         System.out.println("子类被执行");
4         return map.values();
5     }
6 }

  在父类的前置条件大于子类的前置条件的情况下,场景类实现如下

 1     public static void invoker(){
 2         //父类存在的地方,子类就应该能够存在
 3         Father f = new Father();
 4         HashMap map = new HashMap();
 5         f.doSomething(map);
 6     }
 7     
 8     public static void main(String[] args) {
 9         invoker();
10     }
11 
12 }

 运行结果如下

父类被执行

  引入LSP原则,将父类替换为子类

    public static void invoker(){//父类存在的地方,子类就应该能够存在Son f = new Son();HashMap map = new HashMap();f.doSomething(map);}public static void main(String[] args) {invoker();}}

  运行结果如下

子类被运行

  这就出现了很严重的逻辑混乱问题。在子类没有覆写父类方法的前提下,子类被执行了,这会引起业务逻辑混乱,因为在实际应用中,父类一般都是抽象类,子类是实现类,传递这样一个实现类,就会“歪曲”父类的意图,引起一对意想不到的业务逻辑混乱,所以子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或者更宽松。

覆写或覆写父类的方法时输出结果可以被缩小

  父类的一个方法的返回值是一个类型T,子类的相同方法(重载或覆写)的返回值为S,那么S就必须小于T,也就是要么S和T是同一个类型,要么S是T的子类。这里分两种情况,如果是覆写,父类和子类的同名方法的输入参数是相同的,两个方法的范围值S小于等于T,这是覆写的要求。如果是重载,则要求方法的输入参数类型或数量不相同,在LSP原则下,即子类的输入参数宽于或等于父类的输入参数,也就是说这个方法不会被调用。

 

转载于:https://www.cnblogs.com/aladdin-light/p/5494172.html

这篇关于基于LSP原则对方法的一点相关的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/810790

相关文章

PyTorch核心方法之state_dict()、parameters()参数打印与应用案例

《PyTorch核心方法之state_dict()、parameters()参数打印与应用案例》PyTorch是一个流行的开源深度学习框架,提供了灵活且高效的方式来训练和部署神经网络,这篇文章主要介绍... 目录前言模型案例A. state_dict()方法验证B. parameters()C. 模型结构冻

Python字符串处理方法超全攻略

《Python字符串处理方法超全攻略》字符串可以看作多个字符的按照先后顺序组合,相当于就是序列结构,意味着可以对它进行遍历、切片,:本文主要介绍Python字符串处理方法的相关资料,文中通过代码介... 目录一、基础知识:字符串的“不可变”特性与创建方式二、常用操作:80%场景的“万能工具箱”三、格式化方法

springboot+redis实现订单过期(超时取消)功能的方法详解

《springboot+redis实现订单过期(超时取消)功能的方法详解》在SpringBoot中使用Redis实现订单过期(超时取消)功能,有多种成熟方案,本文为大家整理了几个详细方法,文中的示例代... 目录一、Redis键过期回调方案(推荐)1. 配置Redis监听器2. 监听键过期事件3. Redi

基于SpringBoot实现分布式锁的三种方法

《基于SpringBoot实现分布式锁的三种方法》这篇文章主要为大家详细介绍了基于SpringBoot实现分布式锁的三种方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、基于Redis原生命令实现分布式锁1. 基础版Redis分布式锁2. 可重入锁实现二、使用Redisso

Springboot配置文件相关语法及读取方式详解

《Springboot配置文件相关语法及读取方式详解》本文主要介绍了SpringBoot中的两种配置文件形式,即.properties文件和.yml/.yaml文件,详细讲解了这两种文件的语法和读取方... 目录配置文件的形式语法1、key-value形式2、数组形式读取方式1、通过@value注解2、通过

自定义注解SpringBoot防重复提交AOP方法详解

《自定义注解SpringBoot防重复提交AOP方法详解》该文章描述了一个防止重复提交的流程,通过HttpServletRequest对象获取请求信息,生成唯一标识,使用Redis分布式锁判断请求是否... 目录防重复提交流程引入依赖properties配置自定义注解切面Redis工具类controller

Springboot请求和响应相关注解及使用场景分析

《Springboot请求和响应相关注解及使用场景分析》本文介绍了SpringBoot中用于处理HTTP请求和构建HTTP响应的常用注解,包括@RequestMapping、@RequestParam... 目录1. 请求处理注解@RequestMapping@GetMapping, @PostMappin

Java调用DeepSeek API的8个高频坑与解决方法

《Java调用DeepSeekAPI的8个高频坑与解决方法》现在大模型开发特别火,DeepSeek因为中文理解好、反应快、还便宜,不少Java开发者都用它,本文整理了最常踩的8个坑,希望对... 目录引言一、坑 1:Token 过期未处理,鉴权异常引发服务中断问题本质典型错误代码解决方案:实现 Token

Nginx 访问控制的多种方法

《Nginx访问控制的多种方法》本文系统介绍了Nginx实现Web访问控制的多种方法,包括IP黑白名单、路径/方法/参数控制、HTTP基本认证、防盗链机制、客户端证书校验、限速限流、地理位置控制等基... 目录一、IP 白名单与黑名单1. 允许/拒绝指定IP2. 全局黑名单二、基于路径、方法、参数的访问控制

Python中Request的安装以及简单的使用方法图文教程

《Python中Request的安装以及简单的使用方法图文教程》python里的request库经常被用于进行网络爬虫,想要学习网络爬虫的同学必须得安装request这个第三方库,:本文主要介绍P... 目录1.Requests 安装cmd 窗口安装为pycharm安装在pycharm设置中为项目安装req