设计原则学习之里氏替换原则

2023-11-18

以下内容均来自抖音号【it楠老师教java】的设计模式课程。

1、原理概述

子类对象(objectofsubtype/derivedclass)能够替换程序(program)中父类对象(objectofbase/parentclass)出现的任何地方,并且保证原来程序的逻辑行为(behavior)不变及正确性不被破坏。

2、简单的示例1

// 基类:鸟类
public class Bird {
public void fly () {
System . out . println ( "I can fly" );
} }
// 子类:企鹅类
public class Penguin extends Bird {
// 企鹅不能飞,所以覆盖了基类的 fly 方法,但这违反了里氏替换原则
public void fly () {
throw new UnsupportedOperationException ( "Penguins can't fly" );
}
}
为了遵循LSP,我们可以重新设计类结构,将能飞的行为抽象到一个接口中,让需要飞行能力的鸟类实现这个接口:
// 飞行行为接口
public interface Flyable {
void fly ();
}
// 基类:鸟类
public class Bird {
}
// 子类:能飞的鸟类
public class FlyingBird extends Bird implements Flyable {
@Override
public void fly () {
System . out . println ( "I can fly" );
}
}
// 子类:企鹅类,不实现 Flyable 接口
public class Penguin extends Bird {
}

这里就该明确那些方法是通用的,哪些方法是部分能用的。

比如通用的方法可以放到class bird里。

public void say(){ System.out.println("我属于鸟科")}

public void say(){ System.out.println("我又一双翅膀,尽管不能飞")}

不同的用的方法,可以放到接口里比如有的鸟很小 有的鸟很大

interface  BigBird{ double height()}

interface  SmallBird{ double height()}

上述可能不太准确,但是核心思想就是抽取公共的方法到类里,抽取特殊的方法到接口里。

再举个例 比如

class  door{

//核心方法 只要是门 不管你啥样的 你肯定又面积吧,有价格吧

int price();

int area();

}

但是有的门市防火门 有的是防盗门, 有的是....

interface FangHuo{ void canFangHuo()};

interface FangDao{ void canFangDao()};

3、示例2

我们再来看一个基于数据库操作的案例。假设我们正在开发一个支持多种数据库的程序,包括MySQL、PostgreSQL和SQLite。我们可以使用里氏替换原则来设计合适的类结构,确保代码的可维护性和扩展性。 首先,我们定义一个抽象的Database 基类,它包含一些通用的数据库操作方法, 如 connect() 、disconnect() 和 executeQuery() 。这些方法的具体实现将在 子类中完成。

public abstract class Database {
public abstract void connect ();
public abstract void disconnect ();
public abstract void executeQuery ( String query );
}

然后,为每种数据库类型创建一个子类,继承自 Database 基类。这些子类需要实现基类中定义的抽象方法,并可以添加特定于各自数据库的方法。

这里新手要思考下 为什么用abstract class 怎么不用class 怎么不用interface

不用class,因为我这里还没有具体到那类数据源,其实可以定义个jdbcDatabase ,定义属性driver,url,user ,password。就是更一部的抽取

不用interface 因为connect close 和query是所有数据源都有操作!!!

public class MySQLDatabase extends Database {
@Override
public void connect () {
// 实现 MySQL 的连接逻辑
}
@Override
public void disconnect () {
// 实现 MySQL 的断开连接逻辑
}
@Override
public void executeQuery ( String query ) {
// 实现 MySQL 的查询逻辑
}
// 其他针对 MySQL 的特定方法
}
public class PostgreSQLDatabase extends Database {
// 类似地,为 PostgreSQL 实现相应的方法
}
public class SQLiteDatabase extends Database {
// 类似地,为 SQLite 实现相应的方法
}

 

这样设计的好处是,我们可以在不同的数据库类型之间灵活切换,而不需要修改大量代码。只要这些子类遵循里氏替换原则,我们就可以放心地使用基类的引用来操作不同类型的数据库。例如: 

public class DatabaseClient {
private Database database ;
public DatabaseClient ( Database database ) {
this . database = database ;
}
public void performDatabaseOperations () {
database . connect ();
database . executeQuery ( "SELECT * FROM users" );
database . disconnect ();
}
}
public class Main {
public static void main ( String [] args ) {
// 使用 MySQL 数据库
DatabaseClient client1 = new DatabaseClient ( new MySQLDatabase ());
client1 . performDatabaseOperations ();
// 切换到 PostgreSQL 数据库
DatabaseClient client2 = new DatabaseClient ( new PostgreSQLDatabase ());
client2 . performDatabaseOperations ();
// 切换到 SQLite 数据库
DatabaseClient client3 = new DatabaseClient ( new SQLiteDatabase ());
client3 . performDatabaseOperations ();
}
}

好了,我们稍微总结一下。虽然从定义描述和代码实现上来看,多态和里式替换有点类似,但它们关注的角度是不一样的。多态是面向对象编程的一大特性,也是面向对象编程语言的一种语法。它是一种代码实现的思路。而里式替换是一种设计原则,是用来指导继承关系中子类该如何设计的,子类的设计要保证在替换父类的时候,不改变原有程序的逻辑以及不破坏原有程序的正确性。 

2 、哪些代码明显违背了 LSP
1.子类覆盖或修改了基类的方法

当子类覆盖或修改基类的方法时,可能导致子类无法替换基类的实例而不引起问题。这违反了LSP,会导致代码变得脆弱和不易维护。

public class Bird {
public void fly () {
System . out . println ( "I can fly" );
}
}
public class Penguin extends Bird {
@Override
public void fly () {
throw new UnsupportedOperationException ( "Penguins can't fly" );
}
}
在这个例子中, Penguin 类覆盖了 Bird 类的 fly() 方法,抛出了一个异常。这违反了LSP,因为现在 Penguin 实例无法替换 Bird 实例而不引发问题。

 

2、子类违反了基类的约束条件

当子类违反了基类中定义的约束条件(如输入、输出或异常等),也会违反LSP

public class Stack {
private int top ;
private int [] elements ;
public Stack ( int size ) {
elements = new int [ size ];
top = - 1 ;
}
public void push ( int value ) {
if ( top >= elements . length - 1 ) {
throw new IllegalStateException ( "Stack is full" );
} elements [ ++ top ] = value ;
}
public int pop () {
if ( top < 0 ) {
throw new IllegalStateException ( "Stack is empty" );
}
return elements [ top -- ];
}
}
// 正数的栈
public class NonNegativeStack extends Stack {
public NonNegativeStack ( int size ) {
super ( size );
}
@Override
public void push ( int value ) {
if ( value < 0 ) {
throw new IllegalArgumentException ( "Only non-negative values are allowed" );
}
super . push ( value );
}
}
// 正确的写法
public class NonNegativeStack extends Stack {
public NonNegativeStack ( int size ) {
super ( size );
}
public void pushNonNegative ( int value ) {
if ( value < 0 ) {
throw new IllegalArgumentException ( "Only non-negative values are allowed" );
}
super . push ( value );
}
}

这里感觉给的资料有问题。。。等我看完视频在说 

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

设计原则学习之里氏替换原则 的相关文章

随机推荐

  • 苹果cmsV10-Dplayer播放器插件整合前置广告、暂停广告

    简介 Dplayer播放器 整合前置广告 暂停广告3 0免费版 很多朋友在用maccms的时候会遇到采集的视频资源存在大量的广告 这款Dplayer播放器不经能去除视频里的垃圾广告 还能站长自己添加广告 播放器整合说明 1 整合的苹果cms
  • 程序员整体架构之开发架构

    开发架构 文章目录 开发架构 概述 前言 互联网发展特点 单体架构 面向服务架构 SOA 水平分层架构 微服务架构 水平拆分 垂直拆分 服务网格架构 中台架构 云原生架构 Serverless 架构 小结 公众号 概述 简述了互联网业务发展
  • springboot的多环境配置(测试,开发,生产)

    众所周知再开发过程中 从开发 测试 上线 至少也得有3个环境 然而每个环境的配置都不一样 例如数据库配置 Redis配置 等各种配置 如果在打包环节来一个一个进行修改配置的话 非常容易出错 对于多环境配置 也有很多的构建工具 而他们的原理基
  • unity3d笔记-Input.GetAxis

    关于Input GetAxis 1 Input GetAxis Horizontal 获得键盘上的A D键 2 Input GetAxis Vertical 获得键盘上的W S键 3 Input GetAxis Mouse x 获得鼠标沿屏
  • 图像紫边消除(depurple)

    图像紫边广泛存在于目前的手机摄像头 数码相机 监控摄像头等数字成像系统所得图像中 当我们使用这些设备在逆光 大光圈等条件下拍摄时 所得图像的局部区域 特别是高反差区域 亮暗对比反差很大的图像区域 比如天空 灯管与物体相接的边缘 会比较容易观
  • 通过h5页面上传视频到Linux服务器

    1 上传视频到本地 https www jb51 net article 132531 htm 2 上传视频到Linux服务器 建立ftp连接 保证服务器已经安装ftp及对应端口 帐号有权限 上传视频 https blog csdn net
  • 基于实数编码的遗传算法搜寻多元函数最值

    遗传算法介绍 遗传算法于20世纪70年代由美国的John holland提出 是一种通过模仿达尔文生物进化理论和遗传机制以寻求问题最优解的启发式算法 算法的运作主要依赖于三大算子 选择 交叉 变异 其算法流程如图1所示 图1 遗传算法流程图
  • 作为一枚python小白如何提升项目实战——Python茅台抢购脚本详细教程

    今天给大家推荐的GitHub开源项目就是一款京东抢茅台的脚本 当然推荐的脚本也是仅用于测试和学习研究 禁止用于商业用途 不能保证其合法性 准确性 完整性和有效性 请根据情况自行判断 主要功能 预约茅台 定时自动预约 秒杀预约后等待抢购 定时
  • Python IO编程详解

    一 文件系统操作 1 os os path和pathlib的对比 Python中处理文件路径和文件系统操作的传统方式 是通过os和os path模块中的函数来完成的 这些函数完全能够胜任需求 但往往会使得代码过于冗长 自Python 3 5
  • java随机生成6位数

    生成6位随机数 仅只有6位 int Math random 9 1 100000 Math Random 函数能够返回带正号的double值 该值大于等于0 0且小于1 0 即取值范围是 0 0 1 0 的左闭右开区间
  • 理解 __declspec(dllexport)和__declspec(dllimport)

    这段时间要把tinyxml从静态库弄成动态库 要用到 declspec dllexport 和 declspec dllimport 来导出dll和lib文件 终于弄明白了export和import的作用 下面从使用的角度来说明一下他们的功
  • 2020北京邮电大学计算机学院复试经验分享

    初试组内第4 复试组内第1 综合第2 已成功上岸 最近大家问我复试的比较多 趁还热乎 在这里给大家分享一下吧 仅供参考 然后初试经验贴在这里 不要因为初试成绩不好就放弃复试或者不认真对待 复试是干嘛的就是用来翻盘的 都坚持了一年了 也不差这
  • C# 获取计算机信息

    文章目录 一 本机信息 1 本机名 2 获得本机MAC地址 3 获得计算机名 4 显示器分辨率 5 主显示器分辨率 6 系统路径 二 操作系统信息 1 操作系统类型 2 获得操作系统位数 3 获得操作系统版本 三 处理器信息 1 处理器个数
  • Sublime Text3设置文本的自动换行

    1 点击Preferences Settings 然后出现以下页面 2 点击保存即可 如果想要修改其他属性 可以直接在Default里面找就可以
  • Java正则表达式验证电话号码

    在注册会员时 经常需要输入电话号码 电话号码是指手机号码或者固定电话 如果输入的内容不合法 则会向用户输出提示 本实例模拟实现电话号码的验证功能 接收用户在控制台输入的电话号码 然后进行判断 并将结果输出 在这里使用 Java正则表达式 一
  • linux改变文件所属用户和组

    1 改变文件所属用户 chown 用户名 文件名 2 改变文件所属组 chgrp 用户名 文件名
  • 狂神说-Mybatis笔记(总)

    环境 JDK1 8 MySQL 8 0 23 maven 3 6 1 IDEA2020 3 框架 需要配置文件 官方中文文档 https mybatis org mybatis 3 zh index html 一 简介 1 什么是Mybat
  • 通俗易懂权限管理模块设计-Java

    最近一直在做CMS系统 发现一些内容其实都是重复出现的 例如权限管理模块 权限管理模块就是为了管理用户是否有权利访问某个权限 如果不能则拒绝访问 其实Java中已经有很成熟的权限管理框架 例如 Shiro Spring Security等
  • 怎么让人物脚贴地 模型_Unity利用FinalIK实现角色脚掌贴着地面行走工具

    using System Collections Generic using UnityEditor using UnityEngine public class FootBones public GameObject Bone1 publ
  • 设计原则学习之里氏替换原则

    以下内容均来自抖音号 it楠老师教java 的设计模式课程 1 原理概述 子类对象 objectofsubtype derivedclass 能够替换程序 program 中父类对象 objectofbase parentclass 出现的