里氏替换原则

272 阅读4分钟

这是我参与8月更文挑战的第31天,活动详情查看:8月更文挑战

里氏替换原则

定义

1.通俗来说,子类可以扩展父类的功能,但是不能改变父类原有的功能。

2.在程序中将一个父类对象替换成子类对象,程序将不会产生任何错误和异常,反过来不成立。

3.它是实现开闭原则的重要方式之一,由于使用基类对象的地方都可以使用子类对象,程序中尽量使用父类类型来定义对象,运行时再确立子类类型,用子类对象替换父类对象。

里氏替换原则包含的含义

1.子类可以实现父类的抽象方法,但是不能覆盖父类的非抽象方法。

2.子类可以增加自己特有的方法。

3.当子类覆盖或实现父类方法时,方法前置条件(形参)要比父类的方法更加宽松。 4、当子类的方法实现父类的(抽象)方法时,方法的后置条件(即方法的返回值)要比父类更严格。

继承的作用

1.提高代码重用性

2.多态的前提

方法重写

返回值类型、方法名、参数列表相同构成方法重写

方法重写的两个限制

1.子类重写父类方法时,子类方法的访问修饰符不能比父类更严格。 例子

public class TestExtends {
     public  static void main(String[] args) {
    	 Father f=new Father();
         f.method1();
     }
    
}
class Father{
	public void method1(){
		
	}
}
class Son extends Father{

}

此时我们将父类对象用子类对象来替换,是可以的

public class TestExtends {
     public  static void main(String[] args) {
    	 Father f=new Father();
         f.method1();
     }
    
}
class Father{
	public void method1(){
		
	}
}
class Son extends Father{

}

重写父类的method1方法,发现报错

image.png

原因如下

   public  static void main(String[] args) {
    	 Father f=new Son();
         f.method1();
     }

原先我们调用父类的method1()方法是可以调用的,假如我们在用子类对象替换掉父类对象的时候,子类对象的访问权限为private比父类小,那么无法调用也就无法完成替换,违反了里氏替换原则,重写方法时也就不允许我们这样做。

2.子类重写父类的方法时,子类方法不能抛出比父类更多的异常。

public class TestExtends {
     public  static void main(String[] args) {
    	 Father f=new Son();
         try {
			f.method1();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
     }
    
}
class Father{
	public void method1() throws IOException{
		
	}
}
class Son extends Father{
     public  void method1() {
    	 
     }
}

程序中,父类的method1方法抛出了IO异常,那么我们在调用的时候try catch ,子类方法如果没有抛出异常,子类对象可以直接替换父类对象。
但是如果父类的method1方法没有抛出异常,而子类方法在重写时抛出了异常,可以看到程序报错

image.png

这是由于我们原来在调用父类方法时是不需要try catch操作的,但是在替换成了子类对象之后却需要try catch,此时引用父类的地方无法透明的使用其子类的对象。所以不允许我们这样重写方法。

两个类不能发生继承关系的依据是什么

a.有没有"is a"关系

b.在两个类有了"is a"关系之后,还要考虑子类对象在替换了父类对象之后,业务逻辑是否变化,如果变化,则不能发生继承关系。

正方形和长方形有"is a"关系。

需要考虑业务场景,在特定的业务场景下,正方形替换长方形,可能会导致业务逻辑发生变化。


public class LiskovSubstitutionPrincipleTest {
    public static void main(String[] args) {
    	Rectangle r=new Rectangle();
    	r.setLength(20);
    	r.setWidth(15);
    	System.out.println("Length:"+r.getLength()+","+"Width:"+r.getWidth());
         Utils.change(r);
    	System.out.println("Length:"+r.getLength()+","+"Width:"+r.getWidth());
    	
    	Rectangle square=new Square();
    	square.setLength(12);
    	System.out.println("Length:"+square.getLength()+","+"Width:"+square.getWidth());
    	Utils.change(square);
    	System.out.println("Length:"+square.getLength()+","+"Width:"+square.getWidth());
    	
    	
    }
}
class Rectangle{
	public  double length;
	public  double width;
	public double getLength() {
		return length;
	}
	public void setLength(double length) {
		this.length = length;
	}
	public double getWidth() {
		return width;
	}
	public void setWidth(double width) {
		this.width = width;
	}
	
}
class Square extends Rectangle{
	public double getLength() {
		return length;
	}
	public void setLength(double length) {
		this.length = length;
		this.width=length;
	}
	public double getWidth() {
		return width;
	}
	public void setWidth(double width) {
		this.width = width;
		this.length=width;
	}
	 
}
class Utils{
	public static void  change(Rectangle r) {
		while(r.getWidth()<=r.getLength()) {
			r.setWidth(r.getWidth()+1);
			System.out.println("Length:"+r.getLength()+","+"Width:"+r.getWidth());
		}
		
	}
}

逻辑分析

这里我们需要实现的一个业务场景是,增加长方形的宽直到宽比长多1,但是正方形长等于宽的特性让我们无法实现这个业务场景,也就是说子类对象在替换父类对象之后,改变了原来的业务逻辑。所以正方形在这种场景下无法继承长方形成为它的子类。