我觉得编码是有灵魂的,就像每个人都有信仰一样。那么如何去体现信仰,如何凸显灵魂就需要依赖它所固有的原则。最近学习了设计模式的六大原则,有所感悟,特此做总结和记录。在本文中详细介绍了里氏替换原则(LSP)的定义和理解,同时对它定义的规范进行了举例说明。笔者目前整理的一些blog针对面试都是超高频出现的。大家可以点击链接:http://blog.csdn.net/u012403290
1、前置条件(Pre-condition): 通俗来说就是方法的入参,比如说method(int a),那么这个int a就是前置条件。
2、后置条件(Post-Condition): 通俗来说就是方法的返回,比如说int methd(),那么这个int 就是后置条件。
3、里氏替换原则(LSP): 官方定义如下:
如果对每一个类型为 T1的对象 o1,都有类型为 T2 的对象o2,使得以 T1定义的所有程序 P 在所有的对象 o1 都代换成 o2 时,程序 P 的行为没有发生变化,那么类型 T2 是类型 T1 的子类型。
其实是不太好理解的,通俗来说就是说判断某个继承是否符合LSP规则,需要保证它的子类没有改变父类本有的方法,子类可以拓展,但是不能改变父类原有功能。子类的能力要大于父类,即父类使用的方法子类都可以使用,任何子类的对象都可以当做父类使用。
我说一下自己的想法,首先原则肯定是要遵循的,但是如果过度的遵循里氏替换原则会导致的问题是当你继承一个类之后,完全不能重写父类的方法,这样对于多态性的体现有显得非常的拮据。所以我觉得子类重写父类代码之后,只要不破坏父类原本方法的思想,我觉得都不算是破坏里氏替换原则。为什么笔者会有如此感悟呢?下面例子中我会介绍。
1、子类可以实现父类的抽象方法,但不能覆盖父类的非抽象方法。 首先,如果是抽象方法,在子类继承的时候是强制要实现的,这是多态的一种体现,但是什么叫不能复写父类的抽象方法呢?比如下面的代码:
//父类 package com.brickworkers; import java.util.HashMap; import java.util.Map; /** * * @author Brickworker * Date:2017年4月5日下午2:07:40 * 关于类Base.java的描述:里氏替换原则父类 * Copyright (c) 2017, brcikworker All Rights Reserved. */ public abstract class Base { public void addStr(String a, String b){ System.out.println(a+b); } abstract Map<?, ?> returnMap(HashMap<?, ?> map); } //子类 package com.brickworkers; import java.util.HashMap; import java.util.Map; /** * * @author Brickworker * Date:2017年4月5日下午2:16:52 * 关于类Sub.java的描述:里氏替换原则子类 * Copyright (c) 2017, brcikworker All Rights Reserved. */ public class Sub extends Base{ @Override Map<?, ?> returnMap(HashMap<?, ?> map) {//强制(被动)实现 return null; } @Override public void addStr(String a, String b) {//主动实现 System.out.println(a); } }从上面的子类中可以看出,意思就是禁止主动实现父类中已经实现的方法,目的是防止错误产生。但是为什么会有问题产生呢?请看下面的执行测试:
public static void main(String[] args) { Base b = new Sub(); b.addStr("22", "33"); } //第一种情况,没有重写父类addStr方法,那么执行结果就是 //2233, //如果按上面代码重写了addStr方法,那么执行的结果就是 //22但是我们在现实生活中能做到在父类已实现的方法上不进行一次重写么?个人觉得是不可能的,所以觉得过度遵循规则反倒显得有些墨守成规了。比如说每个类都继承了Object类,我们如果要建立一个自定义两个对象进行比较,那么我们必须重写Object类的equals和hashcode方法,这其实也违背了里氏替换原则,但是多态就显得更加深刻了。
2、子类中可以增加自己特有的方法。
这一条很好理解,意思就是子类继承父类之后,可以拓展自己的功能。建立在上面代码的基础上,我们写个例子:
package com.brickworkers; import java.util.HashMap; import java.util.Map; /** * * @author Brickworker * Date:2017年4月5日下午2:16:52 * 关于类Sub.java的描述:里氏替换原则子类 * Copyright (c) 2017, brcikworker All Rights Reserved. */ public class Sub extends Base { @Override Map<?, ?> returnMap(HashMap<?, ?> map) { return null; } public void print(){ System.out.println("子类拓展方法");//子类增加自己独有方法 } }3、当子类的方法重载父类的方法时,方法的前置条件要比父类方法的输入参数更宽松。 意思就是子类进行方法重载的时候,我们需要对入参更加的放宽,因为子类一定是要比父类强大的,父类能实现,子类也必要能实现:
package com.brickworkers; /** * * @author Brickworker * Date:2017年4月5日下午2:07:40 * 关于类Base.java的描述:里氏替换原则父类 * Copyright (c) 2017, brcikworker All Rights Reserved. */ public abstract class Base { public void print(String str){ System.out.println(str.toString()); } } package com.brickworkers; /** * * @author Brickworker * Date:2017年4月5日下午2:16:52 * 关于类Sub.java的描述:里氏替换原则子类 * Copyright (c) 2017, brcikworker All Rights Reserved. */ public class Sub extends Base { public void print(Object obj) { System.out.println(obj.toString()); } public static void main(String[] args) { Base b = new Sub(); Sub s = new Sub(); b.print("1234"); s.print("1234"); } }4、当子类的方法实现父类的抽象方法时,方法的后置条件要比父类更严格。 子类实现抽象方法是强制的必要实现,如果在不严格控制它的后置条件,比如说子类比父类输入更广,编译器会报错,以下是具体的例子:
package com.brickworkers; /** * * @author Brickworker * Date:2017年4月5日下午2:07:40 * 关于类Base.java的描述:里氏替换原则父类 * Copyright (c) 2017, brcikworker All Rights Reserved. */ public abstract class Base { abstract Object print(String str); } package com.brickworkers; /** * * @author Brickworker * Date:2017年4月5日下午2:16:52 * 关于类Sub.java的描述:里氏替换原则子类 * Copyright (c) 2017, brcikworker All Rights Reserved. */ public class Sub extends Base { @Override String print(String str) { return str; } public static void main(String[] args) { Base b = new Sub(); System.out.println(b.print("1234")); } } //输出:1234看了很多资料和网上的一些文章,我只是站在支持的某一个角度来说明,所有的观点都仅仅是代表自己的学习体会。但是,里氏替换原则的争议非常大,还有很大一部分赞成的是“子类能替代父类被使用,并不表示不能重写父类方法”。总之,对某一样东西的认识和见地,每个人都是不一样的,就想我们小时候看图写话,东西大致是这个东西,每个人的实际表述也是不尽相同的。