设计模式6大原则之里氏替换原则
|
里氏替换原则 定义: 所有引用基类的地方必须能透明地使用其子类的对象。 通俗点讲,只要父类能出现的地方子类就可以出现,而且替换为子类也不会产生任何错误或异常,使用者可能根本就不需要知道是父类还是子类。 但是这里我们需要注意的是:有子类出现的地方,父类未必就能适应。 优点: 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性; 提高代码的重用性; 子类可以形似父类,但又异于父类; 提高代码的可扩展性; 提高产品或项目的开放性。 缺点:继承是侵入性的,只要继承就必须拥有父类的所有属性和方法; 降低代码的灵活性,子类必须拥有父类的属性和方法,让子类增加了约束; 增强了耦合性,当父类的常量、变量和方法被修改时,必须考虑子类的修改。 含义:1、子类必须完全实现父类的方法 例: 如上图,我们知道枪有很多种,而且有一个共同的功能就是射击。Soldier中我们添加了一个KillEnemy()方法,这个方法使用枪来杀敌人,具体使用哪个枪取决于选用哪个 枪。 代码: public abstract class AbstractGun
{
public abstract void Shoot();
}
class Handgun : AbstractGun
{
public override void Shoot()
{
Console.WriteLine("手枪射击");
}
}
class Rifle:AbstractGun
{
public override void Shoot()
{
Console.WriteLine("步枪射击");
}
}
class MachineGun : AbstractGun
{
public override void Shoot()
{
Console.WriteLine("机枪射击");
}
}
public class Soldier
{
private AbstractGun gun;
public AbstractGun Gun
{
set { this.gun = value; }
get { return gun; }
}
public void KillEnemy()
{
Console.WriteLine("士兵开始射击");
gun.Shoot();
}
}
客户端:
static void Main(string[] args)
{
Soldier soldier = new Soldier();
soldier.Gun = new Handgun();
soldier.KillEnemy();
}
注意:在类中调用其他类时务必要用父类或接口,若不能使用,则说明类的设计已经违背LSP原则。 但是现在有一把玩具枪,但是我们知道玩具枪是不能像其它的枪一样射击并击杀敌人的,所以玩具枪不能真正实现Shoot方法。 class ToyGun : AbstractGun
{
public override void Shoot()
{
//玩具枪不能射击杀人,所以不能真正实现此方法
}
}
在这种情况下,我们发现业务调用类已经出现问题了,正常的业务逻辑不能运行。这里有两种处理方法: (1)在Soldier类中增加判断,如果是仿真枪,就不用来杀人。这个方法可以解决问题,但是在程序中,我们每增加一个类,所有与这个父类有关系的类都必须修改,这样就不可行了。所以这个方案被否定了。 (2)ToyGun脱离继承,建立一个独立的父类,可以与AbstractGun建立关联委托关系。类图如下: 通常应用中经常发生如上情况,按LSP原则:若子类不能完整地实现父类的方法,或者父类的某些方法在子类中发生畸变,则建议断开父子继承关系,采用依赖、聚集、组合等关系代替继承。 2、子类可以有自己的个性 因为有这个规则,里氏替换原则不能反过来使用,在子类出现的地方,父类不一定可以胜任。 3、覆盖或者实现父类的方法的时候输入的参数可以被放大
|
