天天看點

設計模式:裡氏替換原則(LSP)[譯文]譯文原文連結

譯文

作為一個Java開發者,我從沒有聽過LSP模式。我隻在讀一些C++相關的東西的時候聽說過這種模式。這非常奇怪,因為這種模式有時候被當作面向對象程式設計的五大原則之一。

這個原則是 Barbara Liskov于1987年第一次提出,并在1994年闡述為:

"假設S是T的子類型,t是T的對象,s是S的對象。如果q(t)能跑通,那麼q(s)也應該能跑通。"

“Let q(x) be a property provable about objects x of type T. Then q(y) should be provable for objects y of type S,where S is a subtype of T.”

換句話說:

  • 如果B是A的子類,
  • A有一個方法f(),
  • 如果b是B的執行個體,a是a的執行個體,
  • 那麼在不修改代碼行為的前提下,所有能調用"a.f()"的地方,也應該能夠調用“b.f()”。

讓我們看一個不遵守LSP原則的繼承的例子。

public class MyOrderedCollection {
    protected List<Integer> list = new ArrayList<>();
 
    public void addElement(Integer i) {
        list.add(i);
    }
 
    public Integer getElement(Integer index) {
        return list.get(index);
    }
}
 
public class MyOrderedAndSortedCollection extends MyOrderedCollection {
    // 重寫 addElement将集合排序
    public void addElement(Integer i) {
        super.addElement(i);
        Collections.sort(super.list);
    }
}
 
public class ExampleLSP1 {
 
    public static void main(String args[]) {
        MyOrderedCollection collection1 = new MyOrderedCollection();
        MyOrderedCollection collection2 = new MyOrderedAndSortedCollection();
        int a = 8, b = 4;
        collection1.addElement(a);
        collection1.addElement(b);
        collection2.addElement(a);
        collection2.addElement(b);
        getAndPrintSecondElement(collection1);
        getAndPrintSecondElement(collection2);
    }
 
    public static void getAndPrintSecondElement(MyOrderedCollection collection) {
        System.out.println("The second element is :"
                + collection.getElement(1));
    }
}      

代碼的運作結果是:

The second element is :4
The second element is :8      

這結果為什麼是一個隐患?

假設開發者A寫了

MyOrderedCollection

。兩年後的某一天,開發者B由于需要使用到排序後的集合,便令

MyOrderedAndSortedCollection

繼承了

MyOrderedCollection

。由于是繼承關系,是以能夠将

MyOrderedCollection

作為參數的函數,

MyOrderedAndSortedCollection

也能作為參數使用。當其中一些函數就是要用到

MyOrderedCollection

中的插入時順序怎麼辦?

為了避免這種情況,開發者B需要檢查所有使用了

MyOrderedCollection

的遺留代碼,并且确認它們是否會引用到

MyOrderedAndSortedCollection

上。根據遺留代碼的數量與複雜程度,這可能會花費好幾周的時間。而且去動現存(在運作)的代碼,并不是一件好事。

下列方式就是遵守LSP原則的一種可行方案:

public interface MyCollection {
 
    abstract public void addElement(Integer i);
 
    abstract public Integer getElement(Integer index);
}
 
public class MyOrderedCollection implements MyCollection {
...
}
 
public class MyOrderedAndSortedCollection implements MyCollection {
...
}      

通過這種配置方式,

MyOrderedCollection

MyOrderedAndSortedCollection

就不一樣了(即使他們實作了同一個接口):

  • 一定要使用插入時順序的代碼就用

    MyOrderedCollection

    ,它不會被

    MyOrderedAndSortedCollection

    修改掉
  • 一段使用了

    MyCollection

    的代碼一定是不會受到插入順序的影響的。是以它既可以是

    MyOrderedCollection

    也可以是

    MyOrderedAndSortedCollection

這種模式可以被視作一種強行為子類型

原文連結

連結:Design Pattern: Liskov’s Substitution Principle (LSP) | Coding Geek (coding-geek.com)

譯者是個菜鳥,初學Java,翻譯僅為了提高提高自身閱讀英文技術貼的能力。如有錯誤還望指正。

侵删。