课程名称:Java设计模式精讲 Debug方式+内存分析
课程章节:第3章 软件设计七大原则
主讲老师:Geely
课程内容:里氏替换原则
里氏替换原则:是实现抽象和多态的具体步骤要求。
问题1)定义?
里式替换就是子类替换父类,不影响之前的代码。
在保证程序逻辑不变的前提下,T1 o1 对象可以被 T2 o2对象所替换。
解释:子类可以扩展父类的功能,但不能改变父类原有的功能。
在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。
解释:
1,一个软件实体如果适用于一个父类的话,那一定适用于它的子类,所有引用父类的地方必须能透明地使用其子类的对象,子类对象能够替换父类对象,而程序逻辑不变。
所有引用基类(父类)的地方必须能透明地使用其子类的对象。
在软件中将一个基类对象替换成它的子类对象,程序将不会产生任何错误和异常,反过来则不成立,如果一个软件实体使用的是一个子类对象的话,那么它不一定能够使用基类对象。例如:我喜欢动物,那我一定喜欢狗,因为狗是动物的子类;但是我喜欢狗,不能据此断定我喜欢动物,因为我并不喜欢老鼠,虽然它也是动物。
问题2)具体表现?
就是子类替换父类,多态的体现。
里氏替换原则可以理解为开闭原则的补充,因为它提供了如何进行扩展。
具体体现:子类可以扩展父类的功能,但不能改变父类原有的功能。
1,子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
2,子类中可以增加自己特有的方法。
3,重载参数更基本(父类):当子类的方法重载父类方法时候,方法的参数要比父类的参数更宽泛(更基础)。(例如父类参数是HashMap,子类参数应该是Map)
4,返回值更严格(子类或不变):当子类的方法重载(重写或实现抽象方法)父类方法时候,方法的放回值,要比父类更严格或相等。
问题3)为什么会有里氏替换原则?
从继承的角度,如果父类中有非抽象方法,子类继承之后,修改了这些非抽象方法,就不算里氏替换原则,这就会带来很多问题。
问题4)如果不遵循里氏替换原则会怎么样?
不遵循里氏替换原则,子类重载了父类的方法,并且参数更子类。就会造成之前调用父类的该方法的地方,都会调用子类的方法,如果子类逻辑和父类逻辑不同,将很容易造成严重的错误。
在扩展的时候问题的风险会增加,里氏替换原则是很好的约束。
问题5)里氏替换原则的优点,它和开闭原则的关系?
优点:
1,约束继承泛滥(只继承不修改),开闭原则的一种体现。
2,加强程序的健壮性,同时变更时也可以做到非常好的兼容性,维护性,扩展性。降低需求变更时引入的风险。
问题6)重载父类方法,方法参数应该更基本。否则父类被重载的方法将永远不会被调用。并且多人合作设计会出现逻辑混乱。(这个遵循开闭原则)
里氏替换原则:子类重载父类的方法时候,参数应该更基本(父类类型或相同类型(相同类型就成了重写))。
如果不遵循该原则,设计的逻辑会出现混乱和父类被子类重载的原父类方法永远不会被执行(如果别人想通过继承你写的子类调用父类方法,是调用不到的)。因为如果重载方法的参数类型收缩,继承该子类的子类,永远不会被用到父类被重载的方法。
/**
- 父类参数是HashMap
*/
public class Base {
public void fun(HashMap hashMap){
System.out.println(“父类方法”);
}
}
/**
- 子类重写父类方法也是可以的。(重写是父类和子类的参数类型和个数一样)
- 但是子类重载父类的方法,参数类型收窄,父类被重载的方法将源不会被调用。
*/
public class child extends Base{
//重写
@Override
public void fun(HashMap hashMap) {
System.out.println(“重写:子类的参数HashMap”);
}
//重载
public void fun(Map map){
System.out.println(“子类的参数Map”);
}
}
/**
- Created by jiash on 2018/12/27.
*/
public class test {
public static void main(String[] args) {
child ch = new child();
ch.fun(new HashMap());
}
}
问题7)不能滥继承?为什么不能滥用继承?
例如:
- 继承关系:正方形继承长方形。
- 业务:当宽<= 长时,宽加1
根据下面的运行情况,如果是上面的业务,长方形执行的结果是预测的结果,正方形就不是预期的结果。所以不要滥用继承。
public class Rectangle {
int length;
int width;
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
}
public class Square extends Rectangle {
int sideLength;
public int getSideLength() {
return sideLength;
}
public void setSideLength(int sideLength) {
this.sideLength = sideLength;
}
@Override
public int getLength() {
return getSideLength();
}
@Override
public void setLength(int sideLength) {
setSideLength(sideLength);
}
@Override
public int getWidth() {
return getSideLength();
}
@Override
public void setWidth(int sideLength) {
setSideLength(sideLength);
}
}
/**
- 继承关系:正方形继承长方形。
- 业务:当宽<= 长时,宽加1
*/
public class test001 {
public static void resize(Rectangle rectangle){
while(rectangle.getWidth() <= rectangle.getLength()){
rectangle.setWidth(rectangle.getWidth()+1);
System.out.println(“宽度”+rectangle.getWidth()+“长度”+rectangle.getLength());
}
System.out.println(“宽度”+rectangle.getWidth()+“长度”+rectangle.getLength());
}
/public static void main(String[] args) {
Rectangle rectangle = new Rectangle();
rectangle.setWidth(10);
rectangle.setLength(20);
resize(rectangle);
}/
public static void main(String[] args) {
Square square = new Square();
square.setLength(10);
resize(square);
}
}
问题7.1)如何解决滥用继承导致业务执行逻辑出现错误的问题呢?
可以通过设计一个接口或抽象方法,让长方形和正方形都继承,来约束继承内容,并且接口内不提供set方法。如果提供set方法,就相当于正方形继承长方形。(根据业务逻辑设计的)
public interface Base {
int getLength();
int getWidth();
}
问题8)返回值类型要收窄(更子类)
例如:父类返回值是Map,子类返回值只能等于或者收窄,可以是Map或Map子类HashMap。
不然会报错,一般的编辑工具都会自动报错。这个问题一般在开发时候不会是真正的问题。
public abstract class Base001 {
public abstract Map fun();
}
public class child001 extends Base001 {
@Override
public HashMap fun() {
HashMap hashMap = new HashMap();
return hashMap;
}
}