一、概念:
里氏替换原则:LSP (Liskov Substitution Principle),如果对每一个类型为T1的对象o1,都有类型为T2的对象o2,使得以T1定义的所有程序P在所有的对象o1都换成o2时,程序P的行为没有变化,那么类型T2是类型T1的子类型。
通俗的定义:所有引用基类的地方必须能透明地使用其子类的对象。
二、例子:
以浇水为例。人,拿到工具【水管、水桶、瓶子】,装水后都可以浇水。【水管、桶、瓶子】都可以获取水。应该有个loadWater方法。有watering 浇水功能。人浇水,人只关注浇水。拿到工具就浇水,不用考虑浇水的细节。流程是,人拿工具,用拿到的工具浇水。
类图如下:
代码如下:
Tools 抽象类:
package dim.LSP.simples; public abstract class Tools { /** * 装水 */ public void loadWater() { } /** * 浇水 */ public void watering() { } }
Bottle瓶子也可以是浇水工具,继承工具类Tools
package dim.LSP.simples; public class Bottle extends Tools{ @Override public void loadWater() { // TODO Auto-generated method stub System.out.println("Bottle load water"); } @Override public void watering() { // TODO Auto-generated method stub System.out.println("bottle watering"); } }
waterPipe类:
package dim.LSP.simples; public class WaterPipe extends Tools{ @Override public void loadWater() { // TODO Auto-generated method stub System.out.println("pipe load water"); } @Override public void watering() { // TODO Auto-generated method stub System.out.println("pipe watering"); } }
Bucket类:
package dim.LSP.simples; public class Bucket extends Tools{ @Override public void loadWater() { // TODO Auto-generated method stub System.out.println("bucket load water"); } @Override public void watering() { // TODO Auto-generated method stub System.out.println("bucket watering"); } }
种植户,浇水的人:
package dim.LSP.simples; public class Planter { Tools tool=null; public Planter() { // TODO Auto-generated constructor stub } public void setTool(Tools tool) { this.tool=tool; } public void waterPlant() { tool.loadWater(); tool.watering(); } }
测试类:
package dim.LSP.simples; public class TestClass { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub <span style="color:#3333ff;"><strong> Planter planter=new Planter(); //用瓶子浇水 planter.setTool(new Bottle()); planter.waterPlant(); //用水管浇水 planter.setTool(new WaterPipe()); planter.waterPlant(); </strong></span> } }
运行结果如下:用瓶子装水,浇水。用水管装水,浇水。
Bottle load water
bottle watering
pipe load water
pipe watering
看测试类代码,浇水的人,拿到工具就浇水。planter 里面:
public void setTool(Tools tool) { this.tool=tool; } public void waterPlant() { tool.loadWater(); tool.watering(); }
测试类里的代码,只要拿了工具,就可以浇水。不用考虑浇水的细节:
Planter planter=new Planter(); //用瓶子浇水 planter.setTool(new Bottle()); planter.waterPlant(); //用水管浇水 planter.setTool(new WaterPipe()); planter.waterPlant();
实现子类对象用父类对象替换。父类能出现的地方,子类就可以出现。也就是概念中的,引用基类的地方必须能透明地使用子类对象。
但是这里有个问题,水管,怎么还要装水。水管直接可以浇水。怎么处理比较合适?可以把水管独立出来,独立为直接浇水的工具,做个单独的抽象类。
类图如下:
package dim.LSP.simples; public abstract class DirectTools { }
DirectTools类,可扩展:
package dim.LSP.simples; public abstract class DirectTools { }
DirectWaterPipe 代码:
package dim.LSP.simples; public class DirectWaterPipe extends DirectTools { Tools tool=new Tools() { @Override public void watering() { // TODO Auto-generated method stub System.out.println("watering directly"); } @Override public void loadWater() { // TODO Auto-generated method stub } }; public Tools getTools() { return tool; } }
测试类:
package dim.LSP.simples; public class TestClass { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub Planter planter=new Planter(); //用瓶子浇水 planter.setTool(new Bottle()); planter.waterPlant(); //用水管浇水 planter.setTool(new WaterPipe()); planter.waterPlant(); <span style="color:#3333ff;"><strong> //用水管直接浇水 planter.setTool(new DirectWaterPipe().getTools()); planter.waterPlant();</strong></span> } }
运行结果:
Bottle load water
bottle watering
pipe load water
pipe watering
watering directly
也可以把DirectWaterPipe 类直接继承Tools,重写loadWater 方法,里面什么也不做。这样有点变扭。
例2:
以视图View为例。View 可以是Button,TextView等等。View 有获取ID,设置ID,监听click等方法。把Button 的对象传给父类View 的对象。
类图如下:
代码如下:
View 抽象类:
package dim.LSP.simples.view; public abstract class View { /** * set the id of view * @return */ public int getId() { return 0; } /** * get the id of view * @param id */ public void setId(int id) { } /** * listener */ public void onClickListener() { } }
Button类,继承View类:
package dim.LSP.simples.view; public class Button extends View{ int btnId=0; @Override public int getId() { // TODO Auto-generated method stub return btnId; } @Override public void setId(int id) { // TODO Auto-generated method stub this.btnId=id; } @Override public void onClickListener() { // TODO Auto-generated method stub System.out.println("click button now"); } }
TextView类:
package dim.LSP.simples.view; public class TextView extends View{ private int textVid=0; @Override public int getId() { // TODO Auto-generated method stub return textVid; } @Override public void setId(int id) { // TODO Auto-generated method stub this.textVid=id; } @Override public void onClickListener() { // TODO Auto-generated method stub System.out.println("click textView now "); } }
Activity类:
package dim.LSP.simples.view; public class Activity { public int getId(View v) { return v.getId(); } public void click(View v) { System.out.println("view Id is "+v.getId()); v.onClickListener(); } }
测试类:
package dim.LSP.simples.view; public class TestClass { public static void main(String[] args) { Activity activity=new Activity(); //设置button ID,按一下,button View btn=new Button(); btn.setId(111); activity.click(btn); //设置TextView id ,按一下TextView View textView=new TextView(); textView.setId(888); activity.click(textView); } }
测试结果:
view Id is 111
click button now
view Id is 888
click textView now
上面的类都做了简单的抽象,如果不用抽象类会如何?
类图如下:
使用者,每次用新工具时,都要,调用loadWater 和watering 。每次用新工具都要修改Planter类。不知道会不会抓狂。抽象了之后,可以屏蔽很多细节。
三、4层含义:
里氏替换原则为良好的继承定义了一个规范,定义包括4层含义:
- 子类可以实现父类的抽象方法。
- 子类中可以增加自己特有的方法。
- 当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。
- 当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。
这里可能会有疑问,为什么不把View和Tools设为接口。感兴趣可以看看这篇文章:接口与抽象类的区别
有所不足、多多指正、共同进步!
相关链接:设计模式六大原则之单一职责原则
参考资料:
《设计模式之禅》
《HeadFirst》
《StartUML详解》