设计模式笔记(二)设计六大原则之二--里氏替换原则
2017-11-06 23:56
489 查看
里氏替换原则
LiskoSubstitution Principle LSP
举个栗子:
士兵开始杀敌…
步枪射击…
即使这个时候我们不想用步枪了,想要用手枪,完全不用更改类设计得代码,只需要在使用的时候给士兵步枪即可:
士兵开始杀敌…
手枪射击…
在类中调用其他类时务必要使用父类或接口,如果不能使用父类或接口,说明类的设计已经违背了LSP原则。
2.如果子类不能完整地实现父类的方法,或者父类的某些方法在子类中已经发生“畸变”,则建议断开父子继承关系,采用依赖,聚集,组合等关系代替继承。
举个栗子:
这个时候我们又有了玩具枪,想一想,玩具枪也是枪啊,理所当然我们又继承了AbstractGun
这个时候如果三毛给的是玩具枪,哦哦
士兵开始杀敌…
玩具枪射击…
发现并没有达到该有的效果。
这个时候我们就需要重新设计一下类的关系。比如玩具枪的声音和形状模拟的枪支,但是它并不属于真枪的一种。
举个栗子:
士兵开始杀敌…
手枪射击…
小兵开始杀敌…
手枪射击…
我们发现执行了子类的方法,明明我们希望所有的士兵不管用什么枪支杀敌都用士兵这个方法,但是子类由于参数范围比父类的小,导致这个时候扭曲了父类的意图,达到了不一样的功能。
覆写的时候,父类和子类的同名方法的输入参数是相同的,两个方法的返回值S小于等于T。这是覆写的要求所在。
重载的话,则要求输入参数类型或数量不同,里氏替换中要求,子类的输入参数宽于或等于父类的输入参数,也就是说这个方法时不会被调用的。
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。这是覆写的要求所在。
重载的话,则要求输入参数类型或数量不同,里氏替换中要求,子类的输入参数宽于或等于父类的输入参数,也就是说这个方法时不会被调用的。
相关文章推荐
- 设计模式学习之——六大设计原则之二:里氏替换原则
- 设计模式学习之——六大设计原则之二:里氏替换原则
- 设计模式六大原则之二:里氏替换原则
- 设计模式学习之——六大设计原则之二:里氏替换原则
- 设计模式六大原则(2):里氏替换原则
- 设计模式六大原则 - (2):里氏替换原则
- 【设计模式】六大原则之二(依赖倒转原则与里氏代换原则)
- 设计模式六大原则(2):里氏替换原则
- 设计模式六大原则(2):里氏替换原则
- 设计模式之六大原则——里氏替换原则(LSP)
- 设计模式六大原则(2)——里氏替换原则
- 设计模式六大原则(2):里氏替换原则
- 设计模式六大原则:里氏替换原则
- 设计模式六大原则(2):里氏替换原则
- 设计模式六大原则(2):里氏替换原则
- 设计模式六大原则(2):里氏替换原则
- 设计模式六大原则之(二)---里氏替换原则
- 设计模式笔记(六)设计六大原则之六--开闭原则
- 设计模式六大原则(2):里氏替换原则
- 设计模式六大原则(2):里氏替换原则