设计原则之里氏替换原则
定义:所有引用基类的地方必须能透明地使用其子类的对象。
问题:有一功能P1,由类A来完成。现在需要将功能P1进行扩展,扩展后的功能为P(P由原有功能P1和新功能P2组成)。
功能P由类A的子类B来完成,子类B在完成新功能P2的同时有可能会导致原有功能P1发生故障。
解决:当使用继承时,遵循里氏替换原则。类B继承类A时,除添加新的方法完成新增功能P2外,尽量不要重写父类A的方法,
也尽量不要重载父类A的方法。
举个栗子:士兵使用武器进行射击,包括武器的类别和特点的介绍。
情况一:士兵使用手枪进行射击,实现结果为:手枪小,便于携带;手枪一次只能发射一颗子弹;手枪开始射击。
1. 新建一个手枪类HandGun,介绍手枪的特点,代码如下:
2. 新建一个士兵类Soldier,包含手枪的相关方法,代码如下:
3. 在类LSPFragment中实现士兵用手枪进行射击,代码如下:
4. 运行后的结果为:
现在我们需要实现士兵使用步枪进行射击,于是我们就要新增步枪类RifleGun,并且要去修改士兵类Soldier。
情况二:士兵使用步枪进行射击,实现结果为:步枪长,不益于携带;步枪可以连续射击;步枪开始射击。
1.新建一个步枪类RifleGun,介绍步枪的特点,代码如下:
2. 修改士兵类Soldier,包含手枪和步枪的相关方法,代码如下:
3. 修改类LSPFragment,分别实现士兵用手枪和步枪进行射击,代码如下:
4. 运行后的结果为:
以上的实现方式可以看出,一旦新增功能(武器),就会去修改类Soldier和类LSPFragment,这样不仅会影响到原有的功能,
扩展起来也十分不方便,假设逻辑比较复杂的话,就需要去修改或重构大量的代码。
于是就要使用里氏替换原则来解决这个问题,新增一个类BaseGun,包含武器的特点和射击动作的方法,让手枪类HandGun和
步枪类RifleGun都继承于它,分别来实现,代码如下:
1. 新增类BaseGun,包含武器的特点和射击动作的方法,代码如下:
2. 让手枪类HandGun和步枪类RifleGun都继承于它,分别来实现以上两个方法,代码如下:
3. 修改士兵类Soldier,让它依赖类BaseGun,代码如下:
4. 修改类LSPFragment,实现士兵用手枪和步枪进行射击,代码如下:
5. 运行后的结果同上。
以上实现方式可以看出,子类(手枪类HandGun和步枪类RifleGun)继承父类BaseGun,并完全实现父类BaseGun中的抽象方法来实现功能。
其中,士兵类Soldier依赖于抽象类BaseGun来调用相应的方法,在类LSPFragment中可以看出,父类都可以用子类来代替,但不能改变父类
原有的功能,在子类中可以任意扩展功能,这就是所谓的“所有引用基类的地方必须能透明地使用其子类的对象。”
下面,我们再新增一个武器类(机枪类CachineGun),并在各个子类中分别扩展功能,比如增加武器名。代码如下:
情况三:
1. 士兵使用手枪进行射击,实现结果为:士兵使用的武器是手枪;手枪小,便于携带;手枪一次只能发射一颗子弹;手枪开始射击。
2. 士兵使用步枪进行射击,实现结果为:士兵使用的武器是步枪;步枪长,不益于携带;步枪可以连续射击;步枪开始射击。
3. 士兵使用机枪进行射击,实现结果为:士兵使用的武器是机枪;机枪长,火力十分凶猛;机枪可以扫射;机枪开始射击。
代码如下所示:
1. 新建一个机枪类CachineGun,介绍机枪的特点,增加机枪独有的方法,代码如下:
2. 修改手枪类HandGun和步枪类RifleGun,分别增加各自独有的方法,代码如下:
3. 此时不需要修改类Solider,只需要在类LSPFragment中增加调用方法,即可实现士兵用手枪、步枪和机枪进行射击,代码如下:
4. 运行后的结果为:
以上,使用继承抽象类来扩展功能,并遵循里氏替换原则,实现起来是不是很方便呀!
原则:子类可以扩展父类的功能,但不能改变父类原有的功能。
父类能出现的地方都可以用子类来代替,而且换成子类也不会出现任何错误或异常,而使用者也无需知道是父类还是子类,
但反过来则不成立。总之,就是抽象。
1. 子类必须完全实现父类的抽象方法,但不能覆盖父类的非抽象方法;
2. 子类中可以增加自己特有的方法;
3. 当子类的方法重载父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数要更宽松;
4.当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
优点:
1. 提高代码的重用性,子类拥有父类的方法和属性;
2. 提高代码的可扩展性,子类可形似于父类,但异于父类,保留自我的特性;
缺点:
1. 继承是侵入性的,只要继承就必须拥有父类的所有方法和属性,在一定程度上约束了子类,降低了代码的灵活性;
2. 增加了耦合,当父类的常量、变量或者方法被修改了,需要考虑子类的修改,所以一旦父类有了变动,很可能会造成
非常糟糕的结果,要重构大量的代码。
总结就是进步,哪怕是一点点。
Github地址:https://github.com/chenxkang