里氏替换由Barbara Liskov女士提出,其给出了两种定义:数组
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.(若是对每个类型为S的对象o1,都有类型为T的对象o2,使得以T定义的全部程序P在全部的对象o1都代换成o2时,程序P的行为没有发生变化,那么类型S是类型T的子类型。)安全
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.(全部引用基类的地方必须能透明地使用其子类的对象。)ide
结合个人理解,我认为里氏替换有两层含义:code
不管采起继承类或实现接口,咱们都应该遵循里氏替换原则,保证职责定义不被破坏,父类引用能安全的被子类对象替换。那么这两种方式,在实际开发中,有什么须要注意的地方,应该怎么处理嘞对象
继承类的优势在于可以实现便捷、直观的共享代码,也能实现多态,但继承是把双刃剑,也有须要注意的地方:继承
基类代码:接口
public class Base { private static final int MAX_NUM = 1000; private int[] arr = new int[MAX_NUM]; private int count; public void add(int number){ if(count<MAX_NUM){ arr[count++] = number; } } public void addAll(int[] numbers){ for(int num : numbers){ add(num); } } }
子类代码:开发
public class Child extends Base { private long sum; @Override public void add(int number) { super.add(number); sum+=number; } @Override public void addAll(int[] numbers) { super.addAll(numbers); for(int i=0;i<numbers.length;i++){ sum+=numbers[i]; } } public long getSum() { return sum; } }
基类的add方法和addAll方法用于将数字添加到内部数组中,子类在此基础上添加了成员变量sum,用于表示数组元素之和。get
public static void main(String[] args) { Child c = new Child(); c.addAll(new int[]{1,2,3}); System.out.println(c.getSum()); }
指望结果是1+2+3=6,但是结果倒是12。为何嘞,这是由于子类调用的父类的addAll方法依赖的add方法同时也被子类重写了,这里先addALL再本身统计一遍和至关于统计了两遍和。it
此时若想正确输出须要咱们把子类的addAll方法修改成:
@Override public void addAll(int[] numbers) { super.addAll(numbers); }
但是,这样又会产生新的一个问题,若是父类修改了add方法的实现为:
public void addAll(int[] numbers){ for(int num : numbers){ if(count<MAX_NUM){ arr[count++] = num; } } }
那么输出又会变为0了。
从这个例子咱们能够看出: 若是父类内部方法可能存在依赖,重写方法不只仅改变了被重写的方法,同时另外一个方法(假设为A)也致使出现了误差,此时若按照原有的职责定义去调用父类的A方法,可能会致使出乎意料的结果。而且,若就算子类在编写时意识到了父类方法间的依赖,修改成正确实现,那么父类就没法自由的修改内部实现了。
这个问题产生的缘由在于咱们重写方法时每每容易只关注父类被重写方法的职责定义,而容易忽视父类其余方法是否存在依赖此方法。致使咱们仍是破坏了父类行为的职责定义,违反了里氏替换原则,其具备必定的隐蔽性。这就要求咱们在编写子类实现的时候必须注意到其余方法受没受影响。同时依赖于内部方法的父类方法也不能随意修改,若被修改方法依赖的方法在其中一个子类被重写。那么就算父类在本类没有改变职责定义,实现结果并无区别,可是若该子类调用,也有可能致使子类预期职责误差的风险。
继承反映的是‘是否是’的关系,假设有两个类,鸟类有会fly()的方法,此时咱们须要添加一个企鹅类,从常识上来看企鹅应该是鸟类的子类。可是因为企鹅的个性,他不能飞,此时就产生了矛盾,本来咱们在父类定义了鸟会飞的职责,按照里氏替换原则,咱们企鹅这个子类的fly()方法必须符合职责定义,可是实际上没法符合,因此就没法实现继承,这与常识相违背。
继承只能继承一个类,相比接口缺乏必定灵活性。
实现接口相比继承就灵活多了,也没有那么多弊端,由于接口仅仅包含职责定义,并无包含代码实现。其优势在于:
可是与继承类的方式相比,也有不足的地方,其不能实现代码的共享,虽然可以在实现类中经过注入公共类,用公共类实现代码共享,可是却没有继承便捷,直观。