Liskov's Substitution Principle
動(dòng)機(jī)
我們進(jìn)行模塊設(shè)計(jì)時(shí)一般都是先建立一些繼承體系,也就是一些抽象基類,然后新建派生類來擴(kuò)展功能。
我們必須確保新的派生子類只是擴(kuò)展基類的功能而沒有將其替換掉。不然的話,在現(xiàn)有模塊引入新建的類的時(shí)候可能會(huì)產(chǎn)生副作用。
LSP 規(guī)定如果程序中模塊使用的是基類,那么基類的引用可以替換成子類,而不會(huì)影響到模塊功能。
目的
派生子類必須能完全替代其基類。個(gè)人理解是,子類和基類對(duì)外暴露的行為必須一致。
例子
以下是違背里氏替換原則的一個(gè)經(jīng)典例子。例子中用到了2個(gè)類:Retangle
和 Square
。我們假設(shè)在程序某處使用到了Retangle
實(shí)例。 接著我們擴(kuò)展應(yīng)用,增加 Square
類。Square
由工廠模式根據(jù)一些條件返回,我們不清楚具體返回什么類型。不過,我們知道返回的肯定是 Retangle
。我們獲取 Retangle
對(duì)象實(shí)例,把寬設(shè)成5,高設(shè)成10,然后得到面積值。對(duì)于長方形而言,寬為5,高為10的話面積應(yīng)該是50,但是結(jié)果卻是100。 這里行為就產(chǎn)生了分歧。
// 違背 LSP
class Rectangle {
protected int m_width;
protected int m_height;
public void setWidth(int width) {
m_width = width;
}
public void setHeight(int height) {
m_height = height;
}
public int getWidth() {
return m_width;
}
public int getHeight() {
return m_height;
}
public int getArea() {
return m_width * m_height;
}
}
class Square extends Rectangle {
public void setWidth(int width) {
m_width = width;
m_height = width;
}
public void setHeight(int height) {
m_width = height;
m_height = height;
}
}
class LspTest {
private static Rectangle getNewRectangle() {
// 假設(shè)這是由一個(gè)工廠來生產(chǎn)的
return new Square();
}
public static void main(String[] args) {
Rectangle r = LspTest.getNewRectangle();
r.setHeight(10);
r.setWidth(5);
// 用戶知道這是一個(gè)長方形, 會(huì)以為自己能夠分別設(shè)置長和寬
// 并且期望得到 長 * 寬 的結(jié)果, 然后就懵逼了
System.out.print(r.getArea());
}
結(jié)論
里氏替換原則是對(duì)開放-關(guān)閉原則的擴(kuò)展,它要求我們必須確保新添加的子類在擴(kuò)展基類功能的同時(shí)不會(huì)改變它們的行為。