以下七种设计原则是软件设计模式尽量遵循的原则,各种原则要求的侧重点不同。
开闭原则 是总纲,它告诉我们要对扩展开放,对修改关闭;
里氏替换原则 告诉我们不要破坏继承体系;
依赖倒置原则 告诉我们要面向接口编程;
单一职责原则 告诉我们实现类要职责单一;
接口隔离原则 告诉我们在设计接口的时候要精简单一;
迪米特法则 告诉我们要降低耦合度;
合成复用原则 告诉我们要优先使用组合或者聚合关系复用,少用继承关系复用。
里氏替换原则(Liskov Substitution Principle, LSP)
如果对每一个类型为 T1的对象O1,都有类型为T2的对象O2,使得以T1定义的所有程序P在所有对象O1都替换成O2时,程序P的行为没有发生变化,那么类型T2是类型T1的子类型。
里氏替换原则是继承复用的基础,它反映了基类与子类之间的关系,是对开闭原则的补充,是对实现抽象化的具体步骤的规范。
里氏替换原则的含义
翻译一下就是:一个软件实体如果使用的是一个父类的话,那么一定适用于其子类,而且察觉不出父类对象和子类对象的区别。也就是说,在软件里面,把父类都替换成它的子类,程序的行为没有变化,简单来说,子类型必须能够替换掉它们的父类型。
只有当子类可以替换掉父类,软件单位的功能不受影响时,父类才能真正被复用,而子类也能够中父类的基础上增加新的行为。也正是由于子类型的可替换性,才使得使用父类类型的模块在无需修改的情况下可以扩展。
里氏替换原则的作用
① 是开闭原则的重要方式之一
② 克服了继承中重写父类造成的可复用性变差的缺点
③ 是动作正确性的保证。即类的扩展不会给已有的系统引入新的错误,降低代码出错的可能性。
壹 几维鸟不是鸟
鸟一般都会飞行,如燕子的飞行速度大概是每小时 120 千米。但是新西兰的几维鸟由于翅膀退化无法飞行。假如要设计一个实例,计算这两种鸟飞行 300 千米要花费的时间。显然,拿燕子来测试这段代码,结果正确,能计算出所需要的时间;但拿几维鸟来测试,结果会发生“除零异常”或是“无穷大”,明显不符合预期,其类图如图 1 所示。
程序运行错误的原因是:几维鸟类重写了鸟类的 setSpeed(double speed) 方法,这违背了里氏替换原则。正确的做法是:取消几维鸟原来的继承关系,定义鸟和几维鸟的更一般的父类,如动物类,它们都有奔跑的能力。几维鸟的飞行速度虽然为 0,但奔跑速度不为 0,可以计算出其奔跑 300 千米所要花费的时间。其类图如图 2 所示。
贰 白马与马
《墨子·小取》中说,“白马,马也;乘白马,乘马也。骊马,马也;乘骊马,乘马也。”(骊马就是黑色的马)
墨子在这里说的是,不论黑马、白马均是马的一种。既然马可以骑,那么白马和黑马必可骑。
这便道出里氏代换的精髓。
里氏替换原则的实现
① 子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。
② 子类中可以增加自己特有的方法。
③ 当子类的方法重载父类的方法时,方法的前置条件(即方法的输入参数)要比父类的方法更宽松。
④ 当子类的方法实现父类的方法时,(重写/重载或实现抽象方法),方法的后置条件(即方法的输出/返回值)要比父类的方法更严格或相等。