浅谈“李氏代换”——从记念金庸和斯坦李提及

李氏代换(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.

特别鸣谢

金庸大侠和斯坦李大英雄带给咱们的青春记忆,以及程杰老师开创的故事体叙述软件设计模式的先河。

相关文章
相关标签/搜索