一、里氏替换原则
里氏替换原则最核心得一句话就是:子类可以扩展基类(父类)的功能,但不能改变父类原有的功能。它包含着四种含义:
- 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
- 子类可以增持自己特有的方法。
- 当子类的方法重载父类的方法时,方法的前置条件(即:方法的参数)要比父类方法的输入参数更为宽松。
- 当子类的方法实现父类的方法时(重写/重载/实现抽象方法),方法的后置条件(即:返回值)要比父类更为更为严格或者相等。
我们先来做一个简单的计算器的功能,创建一个类SumA
,实现一个两数相减的功能reduce()
:
public class SumA {
// 相减
public int reduce(int a,int b){
return a - b;
}
}
再来创建一个类SumB
,增加一个两数相加的功能,并且SumB
是SumA
的子类:
public class SumB extends SumA {
// 相加
public int reduce(int a,int b){
return a + b;
}
}
测试一下:
public static void main(String[] args) {
SumB sumB = new SumB();
System.out.println("5 - 4 = "+sumB.reduce(5,4));
}
结果:
这么看起来结果没有错,那么根据里氏替换原则的定义:一个软件实体如果适用一个父类的话,那一定是适用于其子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。
我们来将对象换成SumA
的子类SumB
的对象再来测试一下:
public static void main(String[] args) {
SumA sumA = new SumB();
System.out.println("5 - 4 = "+sumA.reduce(5,4));
}
结果:
可以看见结果发生了很大的变化,通过仔细查看代码我们发现SumA
的两数相减方法reduce()
和SumB
的两数相加方法reduce()
名字相同。这么来就可以说SumB
重写了SumA
中的非抽象方法reduce()
,并改变了reduce()
方法的行为,使程序发生了很大的漏洞。所以我们来将SumB
类进行改造:
public class SumB extends SumA {
// 相加
public int add(int a,int b){
return a + b;
}
}
在SumB
类中增加一个add()
方法,这样一来SumB
作为子类,既可以调用自己类中的add()
方法,也可以调用父类SumA
中的reduce()
方法。我们再来测试一下:
public static void main(String[] args) {
SumB sumB = new SumB();
System.out.println("5 - 4 = "+sumB.reduce(5,4));
System.out.println("5 + 4 = "+sumB.add(5,4));
}
当然也有人说,如果非要重写父类的方法该怎么办?我这边建议两个方法:
- 将现有的继承关系去掉,让
SumA
和SumB
类都实现同一个接口Sum
类,然后再重写Sum
类中的reduce()
方法。 - 让
SumA
和SumB
都继承一个比较通俗的基类(父类),将现有的继承关系去掉,采用依赖、聚合,组合等关系代替。
二、合成复用原则
合成复用原则的核心是:复用时要尽量使用组合/聚合关系(关联关系),少用继承。
我们先来看一个数据库连接的例子:
// 数据库连接
public class DBConnection {
//MySQL数据连接
public String getConnection(){
return "MySQL数据库连接......";
}
}
// 产品类 dao
public class ProductDAO {
private DBConnection dbConnection;
public void setDbConnection(DBConnection dbConnection) {
this.dbConnection = dbConnection;
}
public void addProduct(){
String connection = dbConnection.getConnection();
System.out.println("使用【"+connection+"】增加产品");
}
}
DBConnection
是一个提供数据库连接的类,目前只支持MySQL
数据库连接的方法。某一天,客户要求增加一个Oracle
数据库连接的产品,那我们先在DBConnection
增加一个getOracleConnection()
的方法,再去修改ProductDAO
类中的代码?这里且不说已经违反了开闭原则,就是各种代码的复制粘贴也让人心烦的,完全不够简洁、优雅。
我们不用去修改ProductDAO
类中的代码,只需要将DBConnection
类的代码改动一下:
// 数据库连接
public abstract class DBConnection {
//数据库连接方法
public abstract String getConnection();
}
如上面的代码,将DBConnection
类改为抽象类,将getConnection()
方法改为抽象方法。这样一来,如果我们需要MySQL
数据库连接,就增加一个MySQLConnection
类来继承DBConnection
类:
public class MySQLConnection extends DBConnection {
@Override
public String getConnection() {
return "MySQL数据库连接......";
}
}
如果我们需要Oracle
数据库连接,就增加一个OracleConnection
类来继承DBConnection
类:
public class OracleConnection extends DBConnection {
@Override
public String getConnection() {
return "Oracle数据库连接......";
}
}
最后在调用ProductDAO类
中的addProduct()
方法前,我们只需要调用setDbConnection()
方法并传入我们所需要的DBConnection
类的子类的对象就可以了。
类图:
最后
设计模式中的七大原则已经讲完了,共有四篇博客,感兴趣的朋友可以去我的博客空间看看。
从下一篇博客开始,我将开始讲解一下Java
中常见的以及我们经常用到的一些设计模式,包括工厂模式、代理模式、单例......如果有兴趣的朋友可以继续关注我,让我们一同进步,谢谢!