一、定义
所有引用基类的地方,必须能透明的使用其子类对象。通俗的说:遵循里氏替换原则的代码,只要父类出现的地方就可以使用子类来替换它而不会产生任何错误,使用者不需要知道用的是父类还是子类。
它的核心是继承
友情提醒:xmind导出的图片有点模糊,请放大查看
二、优缺点
它的核心是继承,它的优缺点也是继承的优缺点
2.1 优点
- 代码共享:子类拥有父类的属性和方法
- 重用性:子类重用父类的代码
- 子父类异同:子类形似父类,异于父类,子父类不同
- 扩展性:子类可以随意扩展父类
- 开放性:父类可以随意被扩展,所以开放性随之增加
2.2 缺点
- 侵入性:继承是具有侵入性的,强制继承父类的属性和方法
- 灵活性:降低了了灵活性,子类必须拥有父类的方法,从子类角度看,子类被父类约束了
- 耦合性:耦合性增强,子类继承了父类的属性和方法,如果修改父类,那么所有子类的逻辑有可能都要修改
三、重点
- 返回值范围
- 子类返回值S,父类返回值F,根据里氏替换原则,F范围必须大于S
- 重载约束
- 在对象调用时,不能让子类的方法被调用;继承父类时,子类不能重写父类的方法;必须重写时子类的方法参数个数大于父类
四、 注意点
如果想要遵循里氏替换原则,那么尽量避免让子类拥有自己单独的属性和方法,子类个性多了,子父类关系将难以调和
五、案例
路边摆摊打气球,我们可以用玩具枪射击,也可以用仿真枪射击。用代码描述出来。
5.1 遵循里氏替换原则代码实现
/**
* 枪的抽象类,包含抽象方法和一个成员变量
*
* Created by rytong on 2017/11/14.
*/
public abstract class IGun {
protected static final String TAG = "IGun";
public abstract void shoot();
}
玩具枪类,仿真枪类似:
/**
* 玩具枪类
* Created by rytong on 2017/11/14.
*/
public class PlayerGun extends IGun {
@Override
public void shoot() {
Log.e(TAG,"用玩具枪打气球");
}
}
打气球的玩家:
/**
* 打气球的玩家
* Created by rytong on 2017/11/14.
*/
public class Player {
IGun gun;
public Player(IGun gun) {
this.gun = gun;
}
public void shoot(){
gun.shoot();
}
}
玩家打气球:
IGun iGun = new PlamingGun();
new Player(iGun).shoot();
IGun iGun1 = new PlayerGun();
new Player(iGun1).shoot();
玩家打了两枪,结果:
Log的TAG是继承的父类,射击方法是实现的父类的抽象方法。
11-14 21:04:49.084 20053-20053/com.designpatterndisclipline E/IGun: 使用仿真枪打气球
11-14 21:04:49.084 20053-20053/com.designpatterndisclipline E/IGun: 用玩具枪打气球
5.2 修改逻辑模拟不严格遵循里氏替换原则可能存在的问题
给IGun增加两个方法,一个内部调用,一个开放出来player调用
/**
* 枪的抽象类,包含抽象方法和一个成员变量
*
* Created by rytong on 2017/11/14.
*/
public abstract class IGun {
protected static final String TAG = "IGun";
public abstract void shoot();
private void preBullet(){
Log.e(TAG,"先准备好子弹才能射击。");
}
public void fire(){
preBullet();
shoot();
}
}
PlayerGun不遵循里氏替换原则,覆盖父类的成员变量和方法,而PlamingGun完全遵循里氏替换原则。
/**
* 玩具枪类
* Created by rytong on 2017/11/14.
*/
public class PlayerGun extends IGun {
private String TAG = "PlayerGun";
@Override
public void shoot() {
Log.e(TAG,"用玩具枪打气球");
}
@Override
public void fire() {
// super.fire();
Log.e(TAG,"我在玩玩具枪,不打气球");
}
}
/**
* 仿真枪类
* Created by rytong on 2017/11/14.
*/
public class PlamingGun extends IGun {
@Override
public void shoot() {
Log.e(TAG,"使用仿真枪打气球");
}
}
修改玩家射击的方法
/**
* 打气球的玩家
* Created by rytong on 2017/11/14.
*/
public class Player {
IGun gun;
public Player(IGun gun) {
this.gun = gun;
}
public void shoot(){
gun.fire();
}
}
玩家射击气球:
IGun iGun = new PlamingGun();
new Player(iGun).shoot();
IGun iGun1 = new PlayerGun();
new Player(iGun1).shoot();
执行结果:
11-14 21:17:50.800 28393-28393/com.designpatterndisclipline E/IGun: 先准备好子弹才能射击。
11-14 21:17:50.800 28393-28393/com.designpatterndisclipline E/IGun: 使用仿真枪打气球
11-14 21:17:50.802 28393-28393/com.designpatterndisclipline E/PlayerGun: 我在玩玩具枪,不打气球
如果设计时规定了要遵循里氏替换原则那么就要严格遵守,否则在编程过程中往往会发生一些难以预料的错误,也就是你的代码的bug率会大大提升。