淺談「李氏代換」——從記念金庸和斯坦李提及

李氏代換(LSP)簡介

李氏代換是軟件設計的一個原則,又名依賴倒轉原則或依賴倒置原則,其衍生原則有接口分離原則等。該原則由Barbara Liskov於1988年提出。java

該原則指出,程序中高級別的元素應當獨立於繼承於它的低級別的元素獨立存在,而程序中低級別的元素的設計和運行應當依賴於(繼承)高級別元素的存在。換句話說,即高級的類、模塊或接口應當不知道低級的類、模塊或接口的存在,而低級的類、模塊或接口的設計、實現以及運行應當繼承高級的類、模塊或接口,從而實現了「強內聚、低耦合」的軟件設計要求。由此原則,能夠衍生出如下一些規則(推論):python

  1. 程序中低級別的元素應當可以徹底用以替代高級別的元素。
  2. 設計接口時應當不考慮實體的具體行爲,使得實體的具體行爲實現能夠創建在實現接口的基礎上進行。
  3. 實體類的具體行爲不該當依賴於與這些行爲無關的接口。

規則2和3即爲接口分離原則。編程

舉個栗子

前不久金庸大俠和斯坦李大英雄都不幸逝世了,這兩位大師的筆下敘述了不少的英雄形象(Hero)的故事,這個例子就與如何對小說影視等做品中出現的英雄人物編程有關。這些英雄人物各有不一樣的故事(IStory)、不一樣的超能力(IAbility),有的還有會飛(IFly)這種行爲。能夠設定爲有超能力的英雄必定有故事,即用IAbility繼承IStory。會飛的英雄必定有故事、而且有超能力,因此能夠繼承上述兩個接口。設計模式

這些英雄形象通常在中國叫做武俠(Wuxia),而在美國叫作超人(SuperHero),所以能夠將Wuxia和SuperHero根據李氏代換原則設置爲Hero的子類,這些類應當實現IAbility接口。有的武俠會飛(FlyingWuxia),所以會飛的武俠能夠繼承武俠並擴展「會飛」的特性,而FlyingWuxia的實體在運行中也能夠用於徹底替代武俠類。相似地能夠建立FlyingSuperHero類。這些會飛的武俠或者超人應當去實現IFly這一接口。安全

接口的實現

在程序設計中,接口名通常以大寫字母「I」開頭,其Java實現以下:app

public interface IStory{
    public void showStory(String whatHappened);
}

public interface IAbility extends IStory {
    public void showAbility(String whatAbility);
}

public interface IFly extends IAbility, IStory{
    public void take_off(String tool);

    public void fly();

    public void landing();
}

Java中接口能夠多繼承,可是類不能夠。Python是個更強調具體實現的語言,因爲(原則上)全部的類都是可實例化、可多繼承的對象,所以沒有了接口這一說。可是咱們仍然可使用abc模塊的ABCMeta、abstractmethod這兩個子模塊進行接口與抽象類(設計上)的實現。標註abstractmethod的目的僅僅是爲了提醒後續的類進行實現(固然與Java不一樣,python中對於這些接口或抽象類的「抽象方法」不做實現也不會影響運行)編程語言

from abc import ABCMeta, abstractmethod

class IStory:
    __metaclass__ = ABCMeta

    @abstractmethod
    def showStory(person, whatHappened):
        print(person, " experienced ", whatHappened)

class IAbility(IStory):
    __metaclass__ = ABCMeta

    @abstractmethod
    def showAbility(person, ability):
        print(person, "has ability", ability)

class IFly(IAbility, IStory):
    __metaclass__ = ABCMeta

    @abstractmethod
    def take_off(thing):
        print(thing, "takes off!")

    @abstractmethod
    def fly(thing):
        print(thing, "flying!")

    @abstractmethod
    def landing(thing):
        print(thing, "landing!")

抽象類的實現

關於Hero這個類的實現,因爲每一個英雄人物都有特定的姓名、性別等特性,但每一個英雄都有不一樣的故事,所以咱們能夠考慮設置英雄爲一個抽象類幷包含抽象方法「它的故事」。ide

「英雄」的Java實現:函數

public abstract class Hero implements IAbility {
    private String name;
    private char gender;
    private String nationality;
    private int age;

    public Hero(String name, char gender, String nationality) {
        this(name, gender, nationality, 20);
    }

    public Hero(String name, char gender, String nationality, int age) {
        this.name = name;
        this.gender = gender;
        this.nationality = nationality;
        this.age = age;
    }

    @Override
    public void showAbility(String whatAbility) {
        System.out.println(this + " has ability " + whatAbility);
    }

    @Override
    public String toString(){
        return name + " " + gender + " " + age + " " + nationality;
    }

    @Override
    public abstract void showStory(String whatHappened);

}

「英雄」的Python實現:工具

class Hero(IAbility):
    def __init__(self, name, gender, nationality, age=20):
        self.__name = name
        self.__gender = gender
        self.__nationality = nationality
        self.__age = age

    def __str__(self):
        return (self.__name + " " +  self.__gender + " " +  str(self.__age) + 
                " comes from " + self.__nationality)

    @abstractmethod
    def showStory(self, whatHappened):
        print(self, " experienced ", whatHappened)

其餘實現

其餘的「武俠」、「超人」等實現,繼承「英雄」類並實現「超能力」接口便可,會飛的英雄人物須要實現「飛行」接口。這都比較簡單,此處就直接上代碼出招,不作贅述。

「武俠」類的Java實現:

public class Wuxia extends Hero{

    public Wuxia(String name, char gender) {
        super(name, gender, "中國", 20);
    }

    public Wuxia(String name, char gender, int age) {
        super(name, gender, "中國", age);
    }

    @Override
    public String toString(){
        return super.toString();
    }

    @Override
    public void showStory(String whatHappened) {
        System.out.println(this + " experienced " + whatHappened);
    }
}

「武俠」類的Python實現:

class Wuxia(Hero):
    def __init__(self, name, gender, age=20):
        Hero.__init__(self, name, gender, "中國", age)

「飛行武俠」類的Java和Python實現:

public class FlyingWuxia extends Wuxia implements IFly{

    public FlyingWuxia(String name, char gender) {
        super(name, gender);
    }

    public FlyingWuxia(String name, char gender, int age) {
        super(name, gender, age);
    }

    @Override
    public String toString(){
        return super.toString();
    }

    @Override
    public void take_off(String tool) {
        System.out.println(this + " uses " + tool + " to take off!");
    }

    @Override
    public void fly() {
        System.out.println(this + " is flying!");
    }

    @Override
    public void landing() {
        System.out.println(this + " is landing!");
    }

    @Override
    public void showStory(String whatHappened) {
        System.out.println(this + " experienced " + whatHappened);
    }

}
class FlyingWuxia(Wuxia, IFly):
    def __init__(self, name, gender, age=20):
        Wuxia.__init__(self, name, gender, age)

    def take_off(self, tool):
        print(self, "uses ability", tool, "to take off!")

「飛行超人」的Java和Python實現:

public class FlyingSuperHero extends Hero implements IFly{

    public FlyingSuperHero(String name, char gender) {
        super(name, gender, "U.S.A");
    }

    public FlyingSuperHero(String name, char gender,int age) {
        super(name, gender, "U.S.A", age);
    }

    @Override
    public String toString(){
        return super.toString();
    }

    @Override
    public void take_off(String tool) {
        System.out.println(this + " uses " + tool + " to take off!");
    }

    @Override
    public void fly() {
        System.out.println(this + " is flying!");
    }

    @Override
    public void landing() {
        System.out.println(this + " is landing!");
    }

    @Override
    public void showStory(String whatHappened) {
        System.out.println(this + " experienced " + whatHappened);
    }

}
class FlyingSuperHero(Hero, IFly):
    def __init__(self, name, gender, age=20):
        Hero.__init__(self, name, gender, "U.S.A", age)

    def take_off(self, tool):
        print(self, "uses tool", tool, "to take off!")

幾點思考

Java和Python都是面向對象的編程語言,關於兩者在面向對象方面的優劣,江湖上各派也各有各的觀點。好比,Python雖然實現簡單,可是它的封裝特性有很大的問題,常常成爲各路黑客們行走江湖、「行俠仗義」或「替天行道」的工具;而Java雖然可以作到類型安全,也體現了不少的設計原則,但畢竟實現過程頗費周折。在「李氏代換」這個原則下,對於這兩個編程語言能夠分別有以下思考:

  1. 這兩種OOP語言都能體現「低級別的元素應當可以徹底用以替代高級別的元素」這一李氏代換衍生規則一。須要注意的區別是,Java的「向下轉化(down-casting)」更是得到了低級別類的行爲在須要時可以代替高級別類的行爲的效果,但也由於開放了對高級別類行爲的修改而違反了「開放-封閉」原則中「對修改封閉」的原則。Python則由於根本沒有down-casting這一出而沒有在此處違反「開放-封閉」原則。
//Java main函數中調用英雄示例:
public static void main(String[] args) {
        Wuxia duanyu = new Wuxia("段譽", 'M', 19);
        duanyu.showAbility("六脈神劍");
        demoStory(duanyu, "趕走慕容復");
        System.out.println("");

        //Python中這下一行是不能夠的
        Wuxia changqing = new FlyingWuxia("徐長卿", 'M', 26);
        changqing.showAbility("蜀山劍法");
        demoTakeOff((FlyingWuxia)changqing, "御劍");
        demoStory(changqing, "當了蜀山長老");
        System.out.println("");

        Hero captain = new FlyingSuperHero("Steven", 'M', 98);
        captain.showAbility("Flying");

		//在Python中這也是不能夠的
        demoTakeOff((FlyingSuperHero)captain, "aegis");
        System.out.println("");
    }

    public static void demoTakeOff(IFly fly, String tool){
        fly.take_off(tool);
    }

    public static void demoStory(IStory story, String whatHappened){
        story.showStory(whatHappened);
    }

# Python調用英雄人物示例:

# If you did this:
# changqing = Wuxia("徐長卿", 'M', 26)
# you cannot do:
# FlyingWuxia(changqing).take_off("御劍") in Python
changqing = FlyingWuxia("徐長卿", 'M', 26)
IAbility.showAbility(changqing, "蜀山劍法")
IStory.showStory(changqing, "當了蜀山長老")
changqing.take_off("御劍")
print()
captain = FlyingSuperHero("Steven", 'M', 98)
captain.take_off("aegis")
print()
boeing757 = Airplane("Boeing 757")
boeing757.take_off()
boeing757.landing()

如下分別是java netbeans和python spyder下的運行效果:

  1. 兩種編程語言都可以體現李氏代換推論2,可是Java因爲強制要求類對於所實現的接口中全部方法必須進行實現,這不免違反了推論3的規則。所以很容易就致使代碼冗長。從上述實現中,讀者能夠發現Python的代碼遠遠要短於Java的代碼。再具體一點,若是隻須要關注做品中的部分情節,好比詳細描寫「起飛」而不須要過分關注怎麼平飛和降落的情形時(好比,「徐長卿利於劍上,運用內功緩緩升起,頃刻便到了長安城。」的描寫中就忽略了平飛和降落的細節),這時明顯Python給出的實現方式更佳。
  2. 經過思考的結論2,很明顯,若是在平常的編碼實現中可以充分應用、活用「李氏代換」的原則,咱們就可以大幅度減小代碼重複性,從而實現碼農早下班、不脫髮的美好理想。

參考資料

  1. 程傑, 大話設計模式. 北京: 清華大學出版社, 2015.
  2. Y. D. Liang, Introduction to Java programming, Tenth edition. Boston: Pearson, 2015.
  3. M. Lutz, Learning Python, Fifth edition. Beijing: O’Reilly, 2013.
  4. C. Giridhar, 「Learning Python Design Patterns - Second Edition,」 p. 311.

特別鳴謝

金庸大俠和斯坦李大英雄帶給咱們的青春記憶,以及程傑老師開創的故事體敘述軟件設計模式的先河。

相關文章
相關標籤/搜索