设计模式六大原则之里氏替换原则
一、概念:里氏替换原则:LSP (Liskov Substitution Principle),如果对每一个类型为T1的对象o1,都有类型为T2o2,使得以定义的所有程序P在所有的对象o1都换成时,程序的行为没有变化,那么类型是类型的子类型。
通俗的定义:所有引用基类的地方必须能透明地使用其子类的对象。 二、例子:以浇水为例。人,拿到工具【水管、水桶、瓶子】,装水后都可以浇水。【水管、桶、瓶子】都可以获取水。应该有个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"); } }
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
看测试类代码,浇水的人,拿到工具就浇水。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 也可以把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 上面的类都做了简单的抽象,如果不用抽象类会如何? 类图如下: 使用者,每次用新工具时,都要,调用loadWater 和watering 。每次用新工具都要修改Planter类。不知道会不会抓狂。抽象了之后,可以屏蔽很多细节。 三、4层含义:里氏替换原则为良好的继承定义了一个规范,定义包括4层含义:
这里可能会有疑问,为什么不把View和Tools设为接口。感兴趣可以看看这篇文章:接口与抽象类的区别
有所不足、多多指正、共同进步!
相关链接:设计模式六大原则之单一职责原则 参考资料: 接口与抽象类的区别 《设计模式之禅》 《HeadFirst》 《StartUML详解》 设计模式六大原则 设计模式之六大原则 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |