1.定义
1.1标准定义:(Liskov Substitution Principle)LSP可表述为在软件中能够使用基类对象,那么也一定能够使用其子类对象。也就是说子类一定是基类,但是基类就不一定是子类了。使用LSP时需要注意几个问题:
(1)子类所有方法必须在父类中声明,或者子类必须实现父类中声明的所有方法。为了保证系统的扩展性,在程序中通常使用父类来进行定义,如果一个方法只存在子类中,父类中不提供相应的声明,则无法再父类对象中直接使用该方法。如果在子类中声明了新的方法,在父类中没有这个方法,那么客户端针对父类编程无法使用子类中新增方法。
(2)在运用里氏代换原则时,尽量把父类设计为抽象类或者接口,让子类继承父类或实现父接口。这实际就是开闭原则。
1.2与开闭原则比较:里氏代换原则是对“开-闭”原则的补充。实现“开-闭”原则的关键步骤就是抽象化。而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。
2.案例
某系统需要实现对重要数据的加密处理,在数据操作类(DataOperator)中需要调用加密类中定义的加密算法,系统提供加密两个的加密类CipherA和CipherB,它们实现不同的加密方法,在Dataoperator中可以选择其中的一个实现加密操作,如下图所示:
3.分析
在本实例中,导致系统灵活性和可扩展性差的本质原因是MainClass和数据操作类(Dataoperator)都针对每一个具体类进行编程,每增加一个具体类都将修改源代码。此时可以将CipherB作为CipherA的子类。根据里氏代换原则所有能够接受CipherA类对象的地方都可以接受CipherB类的对象,因此可以简化操作类和MainClass(客户端类)的代码,而且将CipherA对象替换成CipherB类对象很方便,无需修改任何源代码。如果需要增加一个新的加密类,如CipherC,只需要将CipherC类作为CipherA类和CipherB类的子类即。重构后的内图如下所示:
4.设计
4.1XMLHelper类:主要是从xml文件里获取到类名。
package LiskovSubstitution; import java.io.IOException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /* * 这个类主要是为了从配置文件获取加密类的类名 * * * */ public class XMLHepler { public XMLHepler() { } public String getClassName() { DocumentBuilderFactory factory=DocumentBuilderFactory.newInstance(); DocumentBuilder builder=null; try { builder=factory.newDocumentBuilder(); }catch(ParserConfigurationException e) { e.printStackTrace(); } Document document=null; try { document=builder.parse("LSP.xml");//配置文件在项目目录下 加载配置文件 }catch (SAXException e) { e.printStackTrace(); }catch (IOException e) { e.printStackTrace(); } NodeList nList=document.getElementsByTagName("EncryptionClassName"); return nList.item(0).getFirstChild().getNodeValue();//获取到节点值 } }
4.2CipherA类:加密类的父类。
package LiskovSubstitution; /* * * 加密类:父类 方法在父类中定义 * */ public class CipherA { public CipherA() { // TODO Auto-generated constructor stub } public String encrypt(String plainText) { byte []a=plainText.getBytes(); for(int i=0;i<a.length;i++) { a[i]^=8;//对每个字符进行异或操作 } String reString=new String(a); return reString ; } }
4.3CipherB类:加密类的子类,一种加密算法。
package LiskovSubstitution; public class CipherB extends CipherA{ public CipherB() { } public String encrypt(String plainText) { //为了看到效果 此处就不再进行加密 验证是否调用了此方法 return "这是子类方法"; } }
4.4MainClass类:mian函数,程序入口。
package LiskovSubstitution; public class MainClass { /* * 主函数 调用加密算法 并输出 * */ private XMLHepler xmlHepler=null; private DataOperator operator=null; private CipherA cipher=null;//定义一个加密类 private String testString="good good study";//测试文本 public MainClass() throws InstantiationException, IllegalAccessException, ClassNotFoundException { xmlHepler=new XMLHepler(); operator=new DataOperator(); cipher = (CipherA)Class.forName(xmlHepler.getClassName()).newInstance();//根据xml读取的类名 来实例化相应的对象 operator.setCipherA(cipher ); String result= operator.encrypt(testString); System.out.println(result); } public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException { new MainClass(); } }
4.5DataOperator类:操作类,选择加密类对象。
package LiskovSubstitution; public class DataOperator { private CipherA cipher =null;//加密类 public DataOperator() { } public void setCipherA(CipherA cipherA) { this.cipher =cipherA; } public String encrypt(String plainText) { return cipher.encrypt(plainText); } }
4.6运行效果:
注:我参考的书是清华大学出版社,由刘伟主编的《设计模式》。代码中存在的不足,还请多多指教。