从学习面向对象编程开始,就知道了继承的概念,是面向对象的三大特征之一,但是继承究竟有什么好的呢?凡事都有两面性,在尽享继承带来的好处的同时,它又给我们带来什么弊端呢?这里我们来回顾一下:
好处:
1、代码共享,减少创建类的工作量,每个子类都拥有父类的方法和树形;
2、提高代码的重用性;
3、提高代码的可扩展性;
4、提高项目或产品的开放性;
缺点:
1、继承是侵入式的,只要继承就必须拥有父类所有的树形和方法;
2、降低代码的灵活性,子类必须继承父类的树形和方法,让子类多了很多的约束;
3、增强了耦合性,当父类的变量、常量和方法被修改时,必须考虑到子类的修改,而且在缺乏规范的环境下,这种修改可能带来非常糟糕的接口——大量的代码需要重构。
里氏替换原则就是针对继承的缺点而定义的规范,它的简明的定义如下:
只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本不需要知道是父类还是子类。但是,反过来就不可以了,有子类出现的地方,父类未必就能适应。
简单的定义包含了4层含义:
1、子类必须完全实现父类的方法
举个例子,CS游戏中的枪,如下类图:
public abstract class AbstractGun {
//枪都有一个特征,就是射击
public abstract void shoot();
}
public class HandGun extends AbstractGun {
@Override
public void shoot() {
System.out.println("手枪射击...");
}
}
public class Rifle extends AbstractGun {
@Override
public void shoot() {
System.out.println("步枪射击...");
}
}
public class MachineGun extends AbstractGun {
@Override
public void shoot() {
System.out.println("机枪射击...");
}
}
/**
* 在类中调用其他类时务必使用父类或是接口,如果不能使用父类或是接口,则说明类的设计已经违背了LSP原则
* @author suo
*/
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 soldier = new Soldier();
soldier.setGun(new Rifle());
soldier.killEnemy();
}
}
运行结果如下:
士兵开始杀敌人...
步枪射击...
此时,如果有一个玩具手枪,不能用来射击,杀不死人,该怎么办?首先不能让玩具手枪继承自AbstractGun,因为它没有射击的特征,可以这么来做,让玩具手枪脱离继承,建立一个独立的父类,为了实现代码的重用,可以与AbstractGun建立关联委托关系,如下类图: