设计模式笔记(二)设计六大原则之二--里氏替换原则

里氏替换原则
LiskoSubstitution Principle LSP

定义:

只要父类出现的地方,子类就可以出现,而且替换为子类也不会有任何错误或异常,使用者可能根本不需要知道是父类还是子类。但是反过来就不行了,有子类出现的地方,父类未必能够适应。

此原则包含了4个含义:

子类必须完全实现父类的方法

1.因为里氏替换要求,父类出现的地方子类一定能出现。所以在写方法的时候如果用父类的参数,子类的功能可以根据需要传入。这样既得到了代码的复用,又可以多功能的实现功能。

举个栗子:

/*
 * 父类:枪
 * 有一个抽象方法:杀敌
 */
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 Soldier {
    //定义士兵的枪支 - 这把枪具体是手枪还是步枪等,需要上战场前通过setGun确定
    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 sanMao = new Soldier();
        //给三毛一支枪
        sanMao.setGun(new Rifle());
        //杀敌
        sanMao.killEnemy();
    }
}

士兵开始杀敌…
步枪射击…

即使这个时候我们不想用步枪了,想要用手枪,完全不用更改类设计得代码,只需要在使用的时候给士兵步枪即可:

//三毛用手枪杀敌
sanMao.setGun(new Handgun());
sanMao.killEnemy();

士兵开始杀敌…
手枪射击…

在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,说明类的设计已经违背了LSP原则。

2.如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖,聚集,组合等关系代替继承。

举个栗子:
这个时候我们又有了玩具枪,想一想,玩具枪也是枪啊,理所当然我们又继承了AbstractGun

/*
 * 玩具枪
 */
public class ToyGun extends AbstractGun{

    //可是玩具枪并不能杀人啊。。。
    @Override
    public void shoot() {
        System.out.println("玩具枪射击...");
    }

}

这个时候如果三毛给的是玩具枪,哦哦

//三毛获得了玩具枪
sanMao.setGun(new ToyGun());
sanMao.killEnemy();

士兵开始杀敌…
玩具枪射击…

发现并没有达到该有的效果。
这个时候我们就需要重新设计一下类的关系。比如玩具枪的声音和形状模拟的枪支,但是它并不属于真枪的一种。

/*
 * 我是玩具枪,不是射击杀敌的真枪
 */
public abstract class AbstractToyGun {
    protected AbstractGun gun;

    //可以射击,但是属于我自己的射击,杀不死人的哦
    public abstract void shoot() ;

    public void beauty() {
        System.out.println("我模拟了真枪的外观...");
        gun.beauty();
    };


}
/*
 * 父类:枪
 * 有一个抽象方法:杀敌
 */
public abstract class AbstractGun {
    //枪用来干什么的?杀敌
    public abstract void shoot();

    //外观
    public abstract void beauty();
}
/*
 * 手枪的特点是携带方便,射程短
 */
public class Handgun extends AbstractGun{

    @Override
    public void shoot() {
        System.out.println("手枪射击...");
    }

    @Override
    public void beauty() {
        System.out.println("我有手枪外观...");
    }

}
/*
 * 步枪的特点是射程远,威力大
 */
public class Rifle extends AbstractGun{

    @Override
    public void shoot() {
        System.out.println("步枪射击...");
    }

    //步枪
    @Override
    public void beauty() {
        System.out.println("我有步枪外观...");
    }

}
/*
 * 手枪玩具枪
 */
public class ToyGun2 extends AbstractToyGun{

    public ToyGun2(AbstractGun gun) {
        super.gun = gun;
    }

    @Override
    public void shoot() {
        System.out.println("玩具枪射击...");
    }


}
//我有一个玩具枪,是一个手枪样式的
ToyGun2 toyGun = new ToyGun2(new Handgun());
toyGun.beauty();

子类可以有自己的个性

但是子类出现的地方,父类未必就可以胜任。也就是常说的向下转型是不安全的。

覆盖或实现父类的方法时输入参数可以被放大。

因为加入父类的参数范围比子类大,那么很有可能调用时没有实现正确的方法。

举个栗子:

/*
 * 之前说士兵可以有一把枪用来杀敌,士兵什么枪都可以有,手枪和步枪
 * 这里假设一个普通的小小兵,只可以拿手枪,当然他拿刀什么的,这里只关注枪的部分
 */
public class GeneralPerson extends Soldier{

    public void killEnemy(Handgun _gun) {
        System.out.println("小兵开始杀敌...");
        _gun.shoot();
    }
}
/*
 * 使用枪支的士兵
 */
public class Soldier {
    //士兵杀敌
    public void killEnemy(AbstractGun gun) {
        System.out.println("士兵开始杀敌...");
        gun.shoot();
    }
}
//产生三毛这个士兵
Soldier sanMao = new Soldier();
//手枪杀敌
sanMao.killEnemy(new Handgun());


//产生四毛这个小兵
GeneralPerson siMao = new GeneralPerson();
//手枪杀敌
siMao.killEnemy(new Handgun());

士兵开始杀敌…
手枪射击…
小兵开始杀敌…
手枪射击…

我们发现执行了子类的方法,明明我们希望所有的士兵不管用什么枪支杀敌都用士兵这个方法,但是子类由于参数范围比父类的小,导致这个时候扭曲了父类的意图,达到了不一样的功能。

覆写或实现父类的方法时输出结果可以被缩小。

意思是说,父类的一个方法返回值是一个类型T,子类的相同方法(重写或覆写)的返回值是S,那么里氏替换原则要求s必须小于等于T。也就是说要么S和T是同一个类型,要么S是T的子类。
覆写的时候,父类和子类的同名方法的输入参数是相同的,两个方法的返回值S小于等于T。这是覆写的要求所在。
重载的话,则要求输入参数类型或数量不同,里氏替换中要求,子类的输入参数宽于或等于父类的输入参数,也就是说这个方法时不会被调用的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值