里氏替换原则

2023-05-16

        里氏替换原则主要是发生在父类和子类之间,说到父类和子类,在面向对象的语言中, 继承是必不可少的、 非常优秀的语言机制, 它有如下优点

  • 代码共享,减少创建类的代码量,每一个子类可以拥有父类的方法和属性。
  • 提高代码的重用性。
  • 子类可以形似父类,但又异于父类。
  • 提高代码的扩展性。

        缺点

  • 继承是侵入性的。 只要继承, 就必须拥有父类的所有属性和方法;
  • 降低代码的灵活性。 子类必须拥有父类的属性和方法, 让子类自由的世界中多了些约
    束;
  • 增强了耦合性。 当父类的常量、 变量和方法被修改时, 需要考虑子类的修改, 而且在
    缺乏规范的环境下, 这种修改可能带来非常糟糕的结果——大段的代码需要重复。

目录

定义

含义

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

2.子类可以有自己的个性

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

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

最佳实践


定义

        里氏替换法则有两种定义:

  • 如果对每一个类型为 T1 的对象 o1,都有类型为 T2 的对象 o2,使得以 T1 定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
  • 所有引用基类(父类)的地方必须能透明地使用其子类的对象。

        第二个定义是最清晰明确的,通俗点讲只要父类能出现的地方我子类就可以出现,而且调用子类还不产生任何的错误或异常,调用者可能根本就不需要知道是父类还是子类。但是反过来就不成了,有子类出现的地方,父类未必就能适应。

含义

        里氏替换原则有4层含义。

  • 子类必须完全实现父类的方法。
  • 子类可以有自己的个性。
  • 覆盖或实现父类的方法时输入参数可以被放大。
  • 覆写或实现父类的方法时输出结果可以被缩小。        

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

        子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法.

//父类
public class Father{
    public void add(int a,int b){
        System.out.println(a+"+"+b+"="+(a+b));
    }
}
//子类
public class Son extends Father{
    @Override
    public void add(int a,int b){
        System.out.println(a+"-"+b+"="+(a-b));
    }
}


 public static void main(String[] args){
        System.out.println("父类的运行结果");
        Father father=new Father();
        father.add(1,2);
        //父类存在的地方,可以用子类替代
        //子类B替代父类A
        System.out.println("子类替代父类后的运行结果");
        Son  son=new Son ();
        son.add(1,2);
    }

运行结果:
父类的运行结果
1+2=3
子类替代父类后的运行结果
1-2=-1   

        我们定义的这个add方法是加数,而不是减数,子类破坏了父类的加数方法。

2.子类可以有自己的个性

        子类中可以增加自己特有的方法。

//父类
public class Father{
    public void add(int a,int b){
        System.out.println("父类:"+a+"+"+b+"="+(a+b));
    }
}
//子类
public class Son extends Father{
    public void reduce(int a,int b){
        System.out.println("子类:"+a+"-"+b+"="+(a-b));
    }
}


 public static void main(String[] args){
        Father father=new Father();
        father.add(1,2);
        
        Son son=new Son ();
        son.add(2,3);
        son.reduce(2,1);
    }

运行结果:

父类:1+2=3

父类:2+3=5

子类:2-1=1

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

        当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。

public class Father {
    public Collection doSomething(HashMap map){
        System.out.println("父类被执行...");
    return map.values();}
}


public class Son extends Father {
    //放大输入参数类型,与父类的方法名相同, 但又不是覆写(Override) 父类的方法。 你加
    //个@Override试试看, 会报错的, 为什么呢? 方法名虽然相同, 但方法的输入参数不同, 就
    //不是覆写, 
    public Collection doSomething(Map map){
        System.out.println("子类被执行...");
        return map.values();
    }
}

        我们来调用一下Father 和Son 类的doSomething方法


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

运行结果:

父类被执行了

父类被执行了

        运行结果两个是一样的, 看明白是怎么回事了吗? 父类方法的输入参数是HashMap类型, 子类的输入参数是Map类型, 也就是说子类的前置条件(输入参数类型)的范围扩大了, 子类代替父类传递到调用者中, 子类的方法永远都不会被执行。 这是正确的。

        如果我们把这个前置条件反过来,看下边代码。

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


public class Son extends Father {
    //放大输入参数类型,与父类的方法名相同, 但又不是覆写(Override) 父类的方法。 你加
    //个@Override试试看, 会报错的, 为什么呢? 方法名虽然相同, 但方法的输入参数不同, 就
    //不是覆写, 
    public Collection doSomething(HashMap map){
        System.out.println("子类被执行...");
        return map.values();
    }
}



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

运行结果:

父类被执行了

子类被执行了

        运行结果不一样的, 看明白是怎么回事了吗? 父类方法的输入参数是Map类型, 子类的输入参数是HashMap类型, 也就是说子类的前置条件(输入参数类型)的范围缩小了, 子类代替父类传递到调用者中, 父类的方法永远都不会被执行。 这是错误的,(主要第一条不得重写(覆盖)父类的非抽象(已实现)方法),明白了没啊。

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

        当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。

        

abstract class Father {
    public abstract Map getMap();
}         
class Son extends Father{
     @Override
     public HashMap getMap(){
         HashMap b=new HashMap();
         b.put("b","子类被执行...");
         return b;
     }
}
         
public static void main(String[] args){
      Father father=new Father();
      System.out.println(father.getMap());
}

        注意:HashMap是Map的子类,        

        父类的一个方法的返回值是一个类型T(Map), 子类的相同方法(覆写) 的返回值为S(HashMap), 那么里氏替换原则就要求S(HashMap)必须小于等于T(Map), 上边的案例完全满足这个条件。

        如果我们要是相反,有什么结果?

        程序直接就报错了,什么报错信息了?

报错信息

英文:'getMap()' in 'com.fry.mylibrary.oaid.MiitHelper.Son' clashes with 'getMap()' in 'com.fry.mylibrary.oaid.MiitHelper.Father'; attempting to use incompatible return type

中文:'com.fry.mylibrary.oaid.MiitHelper.Son' 中的 'getMap()' 与 'com.fry.mylibrary.oaid.MiitHelper.Father' 中的 'getMap()' 冲突; 尝试使用不兼容的返回类型 

        在里氏替换原则 要么S和T是同一个类型, 要么S是T的子类, 为什么呢? 分两种情况, 如果是覆写, 父类和子类的同名方法的输入参数是相同的, 两个方法的范围值S小于等于T, 这是覆写的要求, 这才是重中之重, 子类覆写父类的方法, 天经地义。 如果是重载, 则要求方法的输入参数类型或数量不相同, 在里氏替换原则要求下, 就是子类的输入参数宽于或等于父类的输入参数, 也就是说你写的这个方法是不会被调用的, 参考上面讲的前置条件。

最佳实践

        在项目中, 采用里氏替换原则时, 尽量避免子类的“个性”, 一旦子类有“个性”, 这个子
类和父类之间的关系就很难调和了, 把子类当做父类使用, 子类的“个性”被抹杀——委屈了
点; 把子类单独作为一个业务来使用, 则会让代码间的耦合关系变得扑朔迷离——缺乏类替
换的标准。

参考书籍:设计模式之禅。
 

        

        

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

里氏替换原则 的相关文章

随机推荐

  • Bulk insert如何导入部分字段的数据

    xfeff xfeff IF OBJECT ID 39 Employee 39 IS NOT NULL DROP TABLE Employee GO CREATE TABLE Employee Id int Name VARCHAR 100
  • SQL SERVER获取索引脚本

    xfeff xfeff 关于如何获取索引脚本的语句很多 xff0c 上次在项目中需要去查询并获取索引脚本 xff0c 所以写了一个简单的查询语句来进行获取 WITH idxcol AS SELECT i object id i index
  • 超详细一文到底!软件测试基本流程

    前言 xff1a 采用通用的测试流程 xff0c 能高效 高质量的完成软件测试工作 xff0c 有助于减少沟通成本 xff0c 对各阶段产出有明确认知等等 最终目标 xff1a 实现软件测试规范化 标准化 以下为非通用标准 xff0c 仅供
  • shell:重启&&关机

    文章目录 shutdownhaltpoweroffrebootinitsync shutdown 关机重启命令 shutdown h 10十分钟后关机shutdown h 0马上关机shutdown h now马上关机shutdown c取
  • 世界上最经典的25句话

    1 记住该记住的 xff0c 忘记该忘记的 改变能改变的 xff0c 接受不能改变的 2 能冲刷一切的除了眼泪 xff0c 就是时间 xff0c 以时间来推移感情 xff0c 时间越长 xff0c 冲突越淡 xff0c 仿佛不断稀释的茶 3
  • Android Intent 用法总结

    From xff1a https www jianshu com p 67d99a82509b Android 中提供了 Intent 机制来协助应用间的交互与通讯 xff0c Intent 负责对应用中一次操作的动作 动作涉及数据 附加数
  • centos8 配置vsftpd的SSL/TLS功能

    前面我带着大家已经配置了一个vsftpd服务器 xff08 虚拟用户模式 xff09 xff0c 匿名用户和本地用户模式配置起来比较简单就没再赘述 xff0c 本文再带大家开启vsftpd的SSL TLS功能 1 生成一个TLS证书 Vsf
  • ubuntu gnome 桌面增加快捷方式

    方法 在 ubuntu 的桌面增加快捷方式很简单 xff0c 在 usr share applications 下 xff0c 增加一个 desktop 文件就可 xff0c 内容基本如下 xff1a Desktop Entry Versi
  • DOS那一代的程序员现在都干嘛呢?

    亿友论坛 DOS那一代的程序员现在都干嘛呢 xff1f xff08 几年前的老帖子 xff09 作者 xff1a 包子夹蛋 发布时间 xff1a 2005 5 10 14 01 00 DOS那一代的程序员现在都干嘛呢 xff1f xff08
  • KVM虚拟机掉电重启后无法ssh连接访问

    KVM虚拟机掉电重启无法ssh连接访问 问题描述问题解决过程解决方法 问题描述 KVM虚拟机升级ssh版本后 xff0c 主机因为维护掉电 xff0c 等主机上电后 xff0c 虚拟机重启后 xff0c 再也无法ssh连接访问 问题解决过程
  • 让Ubuntu 18.04系统支持root用户登录的方法

    简介 默认的Ubuntu 18 04系统在登陆界面上是不支持root用户直接登录的 xff0c 但是你可以使用下面的方法让Ubuntu 18 04也支持root登录 通常情况下 xff0c 在Ubuntu 18 04中的普通用户只能通过运行
  • 这十个css动画案例惊艳众人

    大家好 我是前端实验室的大师兄 对于网页设计师和开发工程师而言 xff0c 创建一款极具趣味性和实用性的CSS网页动画 xff0c 能让网站美观不少 CSS动画 xff0c 就是通过CSS代码搭建网页动画 允许设计师和开发人员 xff0c
  • java线程和进程(阻塞队列)

    目录 1 阻塞队列简介 2Java中的阻塞队列 3 阻塞队列的实现原理 4 阻塞队列的使用场景 1 阻塞队列简介 阻塞队列常用于生产者和消费者的场景 xff0c 生产者是往队列里添加元素的线程 xff0c 消费者是从队列里拿元素的线程 阻塞
  • 子类可以重载父类的方法吗?重载会发生父类和子类之间吗?

    不可以 方法重载 xff1a 在同一个类中 xff0c 方法名相同 xff0c 参数列表不同的方法 xff0c 同一个类中 xff01 xff01 xff01 xff0c 子类和父类不是一个类啊 底层原理是方法区加载了子类和父类 xff0c
  • 时间复杂度和空间复杂度(基础,详细)

    前言 算法对于开发人员是非常重要的 xff0c 我们从常见的算法看起 xff0c 比如排序算法 xff0c 排序算法有好几种实现方法 xff0c 最简单的嵌套两个for循环进行排序 xff0c 进阶点就可以用 冒泡排序 xff0c 最终的结
  • 类与类之间的关系

    类之间的关系有 xff1a 泛化 xff08 继承 xff09 实现 关联 聚合 组合 依赖 目录 1 泛化 xff08 Generalization xff09 继承 2 实现 xff08 Realization xff09 3 关联 x
  • Unity调试Android

    Unity调试android xff0c 有两种方式 xff0c 第一种在unity编辑器中查看日志 xff0c 另一种是在android studio查看日志 xff0c 个人比较推荐android studio查看日志 xff0c 主要
  • 一文讲明白Linux中的umask原理及应用

    大家在学习Linux操作系统的时候肯定遇到过umask 大部分的书籍会对umask从原理到各种情况下的应用都讲的非常详细 xff0c 洋洋洒洒一大篇 xff0c 然后呢 然后你就看得云里雾里似乎明白了 xff0c 可是又不太懂这个东西干嘛搞
  • Unity Remote5 使用

    Unity Remote是Unity公司提供的一个移动端同步调试工具 xff0c 在Unity编辑器中以播放模式运行项目时 xff0c 该应用程序将与Unity连接 编辑器的可视输出被发送到设备的屏幕 xff0c 实时输入被发送回Unity
  • 里氏替换原则

    里氏替换原则主要是发生在父类和子类之间 xff0c 说到父类和子类 xff0c 在面向对象的语言中 xff0c 继承是必不可少的 非常优秀的语言机制 xff0c 它有如下优点 xff1a 代码共享 xff0c 减少创建类的代码量 xff0c