6大設計原則(2):里氏替換原則

里氏替換原則: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個含義所講到的,子類的輸入參數寬於或等於父類的參數,也就是說這個方法時不會被調用的。

 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章