里氏替换原则:LSP

定义:

假设对于每个类型为S的对象o1。都有类型为T的对象o2。使得以T定义的全部程序P在全部的对象o1都换为o2时,程序的行为没有发生变化。那么S是T的子类型。

 

在继承的时候,父类出现的地方子类就能够出现,子类可替代父类,由于子类中有父类的方法,然而父类却不能够替代子类,由于子类中可能有父类没有的方法。这就是所谓的向下转型是不安全的。

 

使用继承有非常多长处,能够提高代码的重用性。提高可扩展性、开放性,可是不可否认,继承也是有缺点的:

1.继承是侵入性的,仅仅要继承,就必须拥有父类的全部属性和方法;

2.减少代码的灵活性

3.增强了耦合性。

解决方式就是里氏替换原则。

 

4个含义:

1.子类必须全然实现父类的方法

2.子类能够有自己的方法

3.覆盖或实现父类的方法时,输入參数能够被放大

4.覆写或实现父类的方法时。输出结果能够被缩小

 

前两个含义比較好理解。这里就不再赘述,主要说一下3和4。

先说第3个,覆盖或实现父类的方法时。输入參数能够被放大。

先看一个样例:

class Father {
	public Collection dosomething(HashMap map) {
		System.out.println("父类被运行--->");
		return map.values();
	}
}

class Son extends Father {
	public Collection dosomething(Map map) {
		System.out.println("子类被运行--->");
		return map.values();
	}
}

public class Client {
	public static void main(String[] args) {
		// 父类存在的地方就能够替换为子类
		Father f = new Father();
		HashMap map = new HashMap();
		f.dosomething(map);
	}
}


 

代码执行结果是:

父类被运行--->

 

依据里氏替换原则,将父类改为子类:

 

public class Client {
	public static void main(String[] args) {
		// 父类存在的地方就能够替换为子类
		// Father f = new Father();
		Son f = new Son();
		HashMap map = new HashMap();
		f.dosomething(map);
	}
}


 

然而输出结果还是父类被运行。

。。

父类方法的參数是HashMap。而子类方法的參数是Map。也就是说子类的參数类型范围大,子类取代父类传递到调用者中,子类的方法永远不会被运行。

假设想要运行子类中的方法的话就须要覆写父类中的方法。覆写就是父类中的方法一模一样的出如今子类中。

 

class Father {
	public Collection dosomething(HashMap map) {
		System.out.println("父类被运行--->");
		return map.values();
	}
}

class Son extends Father {
	// public void dosomething(Map map) {
	// System.out.println("子类被运行--->");
	// }
	@Override
	public Collection dosomething(HashMap map) {
		// TODO Auto-generated method stub
		System.out.println("子类被运行--->");
		return map.values();
	}
}

public class Client {
	public static void main(String[] args) {
		// 父类存在的地方就能够替换为子类
		// Father f = new Father();
		Son f = new Son();
		HashMap map = new HashMap();
		f.dosomething(map);
	}
}


 

这是正常的。

 

 

假设父类參数的參数类型范围大于子类输入參数类型的话,会出现什么问题呢?会出现父类存在的地方,子类就未必能够存在,由于一旦把子类作为參数传入,调用者就非常可能进入子类的方法范畴。

改动一下上面的代码。扩大父类參数范围。缩小子类參数范围。

 

class Father {
	public Collection dosomething(Map map) {
		System.out.println("父类被运行--->");
		return map.values();
	}
}

class Son extends Father {
	public Collection dosomething(HashMap map) {
		System.out.println("子类被运行--->");
		return map.values();
	}

}

public class Client {
	public static void main(String[] args) {
		// 父类存在的地方就能够替换为子类
		Father f = new Father();
		Son f1 = new Son();
		HashMap map = new HashMap();
		f.dosomething(map);
		f1.dosomething(map);
	}
}

 

f运行父类的方法,f1运行子类的方法。

 

这就不正常了

 

子类在没有覆写父类方法的情况下。子类方法被运行了。所以,子类中方法的參数范围(前置条件)必须与父类的參数范围(前置条件)同样或者更加宽松。

 

再来说一下第4个含义,覆写或实现父类的方法时,输出结果能够被缩小。

什么意思呢?父类方法的返回值是类型T,子类同样方法(重载或覆写)的返回值是S,那么里氏替换原则就要求S必须小于等于T。也就是说。要么S和T类型同样,要么S是T的子类。

对于覆写而言,父类和子类中的方法时一模一样的,所以返回类型也应当是一样的。

对于重载,也就是第3个含义所讲到的,子类的输入參数宽于或等于父类的參数,也就是说这种方法时不会被调用的。