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

以下内容均来自抖音号【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 );
}
}

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

相关推荐
西岸行者4 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意4 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码4 天前
嵌入式学习路线
学习
毛小茛4 天前
计算机系统概论——校验码
学习
babe小鑫4 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms4 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下4 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。4 天前
2026.2.25监控学习
学习
im_AMBER4 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J4 天前
从“Hello World“ 开始 C++
c语言·c++·学习