基于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

相关文章

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain

macOS无效Launchpad图标轻松删除的4 种实用方法

《macOS无效Launchpad图标轻松删除的4种实用方法》mac中不在appstore上下载的应用经常在删除后它的图标还残留在launchpad中,并且长按图标也不会出现删除符号,下面解决这个问... 在 MACOS 上,Launchpad(也就是「启动台」)是一个便捷的 App 启动工具。但有时候,应

SpringBoot日志配置SLF4J和Logback的方法实现

《SpringBoot日志配置SLF4J和Logback的方法实现》日志记录是不可或缺的一部分,本文主要介绍了SpringBoot日志配置SLF4J和Logback的方法实现,文中通过示例代码介绍的非... 目录一、前言二、案例一:初识日志三、案例二:使用Lombok输出日志四、案例三:配置Logback一

Python实现无痛修改第三方库源码的方法详解

《Python实现无痛修改第三方库源码的方法详解》很多时候,我们下载的第三方库是不会有需求不满足的情况,但也有极少的情况,第三方库没有兼顾到需求,本文将介绍几个修改源码的操作,大家可以根据需求进行选择... 目录需求不符合模拟示例 1. 修改源文件2. 继承修改3. 猴子补丁4. 追踪局部变量需求不符合很

mysql出现ERROR 2003 (HY000): Can‘t connect to MySQL server on ‘localhost‘ (10061)的解决方法

《mysql出现ERROR2003(HY000):Can‘tconnecttoMySQLserveron‘localhost‘(10061)的解决方法》本文主要介绍了mysql出现... 目录前言:第一步:第二步:第三步:总结:前言:当你想通过命令窗口想打开mysql时候发现提http://www.cpp

Mysql删除几亿条数据表中的部分数据的方法实现

《Mysql删除几亿条数据表中的部分数据的方法实现》在MySQL中删除一个大表中的数据时,需要特别注意操作的性能和对系统的影响,本文主要介绍了Mysql删除几亿条数据表中的部分数据的方法实现,具有一定... 目录1、需求2、方案1. 使用 DELETE 语句分批删除2. 使用 INPLACE ALTER T

MySQL INSERT语句实现当记录不存在时插入的几种方法

《MySQLINSERT语句实现当记录不存在时插入的几种方法》MySQL的INSERT语句是用于向数据库表中插入新记录的关键命令,下面:本文主要介绍MySQLINSERT语句实现当记录不存在时... 目录使用 INSERT IGNORE使用 ON DUPLICATE KEY UPDATE使用 REPLACE