以下内容均来自抖音号【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
);
}
}
这里感觉给的资料有问题。。。等我看完视频在说