里氏替换原则,简称LSP
定义:所有应用基类的地方必须能透明的使用其子类的对象
释义:只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类,但是反过来就不行了,有子类出现的地方,父类未必就能适应
使用里氏替换原则需要注意的以下问题:
1. 子类的所有方法必须在父类中声明,或者说子类必须实现父类中声明的所有方法。根据里氏替换原则,为了保证系统的扩展性,在程序中尽量使用父类类型来对对象进行定义,而在运行时在确定其子类类型,用子类对象来替换父类对象,如果一个方法只存在子类中,在父类中不提供相应的声明,则无法在以父类定义的对象中使用该方法;
2.在运用里氏替换原则时,尽量把父类设计成抽象类或者接口,让子类继承父类或者实现父类接口,并实现父类中声明的方法,运行时,子类实例替换父类实例,这样可以很方便的扩展系统功能,同时无需修改原有子类代码,增加新的功能可以增加一个新的子类去实现。里氏替换原则是开闭原则的具体实现手段之一;
注意事项:
1.在类中调用其他类时务必要使用父类或者接口,如果不能使用父类或者接口,则说明类的设计已经违背了LSP原则
2.如果子类不能完整的实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖、
聚集、组合等关系代替继承
代码案例:
1. 子类必须完全实现父类的方法
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- ///
- /// 枪支的抽象类
- ///
- public abstract class AbstractGun
- {
- ///
- /// 枪用来干什么的?杀敌!
- ///
- public abstract void shoot();
- }
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- ///
- /// 手枪的实现类
- ///
- public class Handgun : AbstractGun {
- public override void shoot()
- {
- Debug.Log("手枪射击......");
- }
-
- }
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- ///
- /// 步枪的实现类
- ///
- public class Rifle : AbstractGun {
- public override void shoot()
- {
- Debug.Log("步枪射击......");
- }
- }
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- ///
- /// 机枪的实现类
- ///
- public class MachineGun : AbstractGun
- {
- public override void shoot()
- {
- Debug.Log("机枪扫射...");
- }
- }
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- ///
- /// 士兵的实现类
- ///
- public class Soldier {
- ///
- /// 定义士兵的枪支
- ///
- private AbstractGun gun;
-
- ///
- /// 给士兵一支枪
- ///
- ///
- public void SetGun(AbstractGun gun)
- {
- this.gun = gun;
- }
-
- public void KillEnemy()
- {
- Debug.Log("士兵开始杀敌人......");
- gun.shoot();
- }
- }
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- ///
- /// 场景类
- ///
- public class Client : MonoBehaviour {
-
- void Start () {
- //产生一个士兵
- Soldier soldier = new Soldier();
- //给士兵一支步枪
- soldier.SetGun(new Rifle());
- soldier.KillEnemy();
- }
- }
2. 子类可以有自己的个性
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- ///
- /// 狙击枪的实现类
- ///
- public class AUG : Rifle {
-
- public void zoomOut()
- {
- Debug.Log("通过望远镜观察敌人......");
- }
- public void shoot()
- {
- Debug.Log("AUG开始射击......");
- }
- }
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- ///
- /// 狙击手的实现类
- ///
- public class Snipper
- {
- public void KillEnemy(AUG aug)
- {
- //先观察以下敌人
- aug.zoomOut();
- //开始射击
- aug.shoot();
- }
- }
- using System.Collections;
- using System.Collections.Generic;
- using UnityEngine;
-
- ///
- /// 场景类
- ///
- public class Client : MonoBehaviour {
-
- void Start () {
- //产生一个士兵
- Soldier soldier = new Soldier();
- //给士兵一支步枪
- soldier.SetGun(new Rifle());
- soldier.KillEnemy();
-
- //产生一个狙击手
- Snipper snipper = new Snipper();
- //给狙击手一支狙击枪
- snipper.KillEnemy(new AUG());
- }
- }
3. 覆盖或实现父类的方法时输入参数可以被放大
(1)父类存在的地方,子类就可以存在,相反,子类存在的地方,父类不一定存在
(2)子类中方法的前置条件必须与超类中被覆写的方法的前置条件相同或者更宽松
4. 覆写或实现父类的方法时输出结果可以被缩小
父类的一个方法的返回值是一个类型T,子类的相同方法(重载或覆写)的返回值为S,那么里氏替换原则就要求S必须小于等于T,也就是说,要么S和T是同一个类型,要么S是T的子类