面向对象的7种设计原则(5)-里氏代换原则
定义
里氏代换原则(Liskov Substitution Principle LSP)面向对象设计的基本原则之一。里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP是继承复用的基石,只有当衍生类可以替换掉基类,软件单位的功能不受到影响时,基类才能真正被复用,而衍生类也能够在基类的基础上增加新的行为。里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
原则
第一点
子类必须实现父类的抽象方法,但不得重写(覆盖)父类的非抽象(已实现)方法。
class Foo {
public void cal(int num1, int num2) {
int value = num1 + num2;
System.out.println("父类计算结果: " + value);
}
}
class Son extends Foo {
public void cal(int num1, int num2) {
int value = num1 - num2;
System.out.println("子类计算结果:" + value);
}
}
class Cal{
public static void main(String[] args) {
Foo foo = new Foo();
foo.cal(2,1);
Son son = new Son();
son.cal(2,1);
}
}
在类的继承中,我们的父类定义好的方法,并不会强制要求其子类必须完全遵守该方法的实现规则。子类是可以修改它继承自父类的任意方法的。 在本例中,父类的本意是想要定义一个两数相加的方法,但是子类继承该方法后却修改为减法,并且也成功了。子类这样操作后,会对整个继承体系造成破坏。当你想把使用父类的地方替换为其子类时,会发现原来的正常的功能现在出现问题了。
第二点
当子类需要重载父类中的方法的时候,子类方法的形参(入参)要比父类方法输入的参数更宽松(范围更广)。
class Foo {
public void method(List arrayList) {
System.out.println("父类方法执行");
}
}
class Son extends Foo {
public void method(ArrayList list) {
System.out.println("子类方法执行" );
}
}
class Cal{
public static void main(String[] args) {
ArrayList list = new ArrayList();
Foo foo = new Foo();
Son son = new Son();
System.out.println("使用父类对象调用的结果:");
foo.method(list);
System.out.println("将父类对象替换为子类对象调用结果");
son.method(list);
}
}
//输出
使用父类对象调用的结果:
父类方法执行
将父类对象替换为子类对象调用结果
子类方法执行
我们的本意是希望对象替换后还执行原来的方法的,可结果却发生变化了。
修改
class Foo {
public void method(ArrayList arrayList) {
System.out.println("父类方法执行");
}
}
class Son extends Foo {
//重载了父类的method,并且方法入参比父类的入仓范围更广
public void method(List list) {
System.out.println("子类方法执行" );
}
}
第三点
重写或者实现父类方法的时候,方法的返回值可以被缩小,但是不能放大。 正例:
class Foo {
public List getList() {
return new ArrayList();
}
}
class Son extends Foo {
public ArrayList getList() {
return new ArrayList();
}
}
反例:
class Foo {
public ArrayList getList() {
return new ArrayList();
}
}
class Son extends Foo {
public List getList() {
return new ArrayList();
}
}
如果我们试图在子类中放大,重写或实现来自父类方法的返回值时,代码会报错,连基本的编译器都无法通过。
第四点
子类可以拥有自己独特的方法或属性
class Foo {
public void cal(int num1, int num2) {
int value = num1 + num2;
System.out.println("父类计算结果: " + value);
}
}
class Son extends Foo {
public void cal(int num1, int num2) {
int value = num1 - num2;
System.out.println("子类计算结果:" + value);
}
public void cal2(int num1, int num2) {
int value = num1 + num2 +num2;
System.out.println("子类计算结果:" + value);
}
}
总结
通过上面的描述相信大家都对里氏替换原则有了一个基本的概念,其实它就是告诉我们在继承中需要注意什么问题和遵守什么规则。
然而在实际开发中我们在很多时候还是会违背该原则的,虽然表面上没有什么特别大的问题,但是这样做会大大增加代码的出错率。我们编写代码时不光要考虑怎么实现该功能,程序的健壮性和后期的扩展以及移植都是需要考虑到的。只有这样做才可以使我们的程序更加优秀。
- SQL Server基础SQL脚本之内外连接、交叉连接;函数、子查询
- MySQL 面试选择题15道(单选)
- SQL Server基础SQL脚本之分区表、分区方案
- SQL Server基础SQL脚本之创建架构、排序
- 枚举算法(Enumeration algorithm)实例一
- 剑指offer代码解析——面试题16反转单链表
- QMainWindow 和 QWidget 设置layout
- 安全工具Aircrack-ng的使用
- Windows Server 2008 R2 搭建微信小程序
- insert事务产生duplicate key error引发的死锁分析
- 零基础入门深度学习 | 第六章:长短时记忆网络(LSTM)
- 第6章 I/O复用:select和poll函数
- 第7章 套接字选项
- 第8章 基本UDP套接字编程
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Shell脚本管道符与重定向
- 偿还技术债(3)-ARouter源码详解
- 偿还技术债(4)-ARouter自己实现一个?
- 偿还技术债(5)-LeakCanary源码详解
- 数据库PostrageSQL-服务器配置(查询规划)
- 别在折腾开发环境了,一劳永逸的 Python 环境搭建方法
- 快速入门Python文件操作
- 使用PyTorch构建的“感知器”网络
- Python 经典面试题 一
- Python 经典面试题 二
- Linux磁盘管理之LVM快速入门配置
- 你熟悉Python的代码规范吗?如何一键实现代码排版
- Deepin安装与基础使用
- Golang 单元测试详尽指引
- Pigeon- Flutter多端接口一致性以及规范化管理实践