1、里氏替换原则来源
继承优点:
- 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性;
- 提高代码的重用性;
- 子类可以形似父类,但又易于父类;
- 提高代码的可扩展性,实现父类的方法就可以“为所欲为”了;
- 提高产品或者项目的开放性;
继承缺点:
- 继承是侵入性的,只要是继承,就必须拥有父类的所有属性和方法;
- 降低代码灵活性,子类必须拥有父类的属性和方法,子类在自由世界中多了些约束;
- 增强了耦合性,当父类的常量,变量或者方法被修改时,需要考虑子类的修改
Java用extends关键字来实现继承,它采用了单一继承的规则,而C++则采用了多重继承的规则,一个子类可以继承多个父类。从总体上看,单继承利大于弊。如何把“利”发挥最大作用,同时减少“弊”带来的问题。解决方案就是引入里氏替换原则(LSP)。
- 如果对每一个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的所有程序P在所有的对象o1都替换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。
- 所有引用基类的地方必须能透明地使用其子类的对象。
对上面第二种解释为“只要父类能出现的地方,子类都可以出现,而且替换为子类也不会产生任何错误或者异常,使用者不需要知道是父类还是子类。但是反过来就不行了,有子类出现的地方,父类未必就能适应。”
2、里氏替换原则规则
a)、子类必须完全实现父类的方法
我们先定义一个枪的接口
public abstract class AbstractGun { //枪用来干什么的?杀敌! public abstract void shoot(); }
我们定义枪的实现类
public class Handgun extends AbstractGun{ public void shoot() { System.out.println("手枪射击..."); } }public class Rifle extends AbstractGun{ public void shoot() { System.out.println("步枪射击..."); } }
public class MachineGun extends AbstractGun{ public void shoot() { System.out.println("机枪射击..."); }
}
定义拥有枪的士兵
public class Soldier { // 定义士兵的枪支 private AbstractGun gun; // 给士兵一支枪 public void setGun(AbstractGun _gun) { this.gun = _gun; } public void killEnemy() { System.out.println("士兵开始杀敌人..."); gun.shoot(); } }
场景类
public class Client { public static void main(String[] args) { //产生三毛这个士兵 Soldier sanMao = new Soldier(); //给三毛一支枪 sanMao.setGun(new Handgun()); sanMao.killEnemy(); } }
运行结果:
士兵开始杀敌人...
手枪射击...
在这个过程,我们给三毛这个士兵一把手枪,然后他就开始杀敌了。如果三毛要用机枪杀敌,sanMao.setGun(new Handgun());换成sanMao.setGun(new MachineGun());所以在编写士兵类的时候根本就不用知道是哪个型号的枪被传入。
注意: 在类中调用其他类时务必要使用父类接口,如果不能使用父类或接口,则该类已经违背了LSP原则
b)子类可以有自己的个性
这个不想过多解释,主要是注意向下转型(downcast)是不安全的,从里氏替换原则来看,就是有子类出现的地方父类未必就可以出现。
c)覆盖或实现父类的方法时输入参数可以被放大
public class Father { public Collection doSomething(HashMap map){ System.out.println("父类被执行..."); return map.values(); } }
public class Son extends Father { //放大输入参数类型 public Collection doSomething(Map map){ System.out.println("子类被执行..."); return map.values(); } }
如上面的例子,子类传入的参数是Map,而父类传入的是HashMap,不错,是重载,子类的参数被放大了,子类代替父类传递到调用者中,子类的方法永远都不会被执行。如果想让子类的方法运行,就必须覆写父类的方法。
如果子类的参数范围比父类小,会引起只调用子类方法,引起程序混乱。
d)覆写或实现父类的方法时输出结果可以被缩小
父类的一个方法的返回值是一个类型T,子类的相同方法(重载或重写)的返回值是S,那么里氏替换原则就要求S必须小于等于T,也就是说,要么S和T是同一个类型,要么S是T的子类
- 重写:父类和子类的同名方法的输入参数是相同的,两个方法的范围值S小于等于T,这是重写的要求(重点);
- 重载:方法的输入参数类型后者数量不相同,在里氏替换原则要求下,就是子类的输入参数宽于或等于父类的输入参数,就是你写的这个方法不会被调用。
采用里氏替换原则的目的就是增强程序的健壮性,版本升级也可以保持很好的兼容性。即使增强子类,原有的子类还可以继续运行。在实际项目中,每个子类对应不同的业务含义,使用父类做参数,传递不同的子类完成不同的业务。
注意:如果采用里氏替换原则,那么尽量避免子类的“个性”,一个子类有个性,这个子类和父类之间的关系就很难调和了,把子类当做父类使用,子类的个性“被抹杀”;把子类单独作为一个业务来使用,则会让代码间的耦合关系变得扑朔迷离,缺乏类替换的标准。
参考:《设计模式之禅》
来源:http://www.cnblogs.com/gudulijia/p/6474378.html