关于Java中继承和接口的理解
Java语言中,为了实现代码重用,设计了继承这一机制,但是,其设计成单继承,这样设计是有原因的,如下图:
Figure1:deadly diamond of death
此图问题称为菱形问题(diamond problem),就是说,当A的子类B和C同时实现了A中的方法,则同时继承了B和C的子类D在调用该方法时会出现混乱,无法得知该调用哪一个方法。
既然不能实现多继承,我们就会考虑把很多方法就写在父类里,或者继承抽象类,实现其方法,但是,这样会导致一个问题,比如说,子类不应该具有父类的某些方法,那么,我么为了解决这个问题,还必须在子类中重写该方法(但是只能在该方法中什么也不干),这样的设计是很不好的。
那么接口就来了,它解决了Java的多继承问题,可以这样理解,接口就是纯粹的抽象类(其中的方法都是public且是抽象的),我们也可以认为接口是“另一种形式的继承(接口继承)”,我们可以这样思考面向对象编程,其本质就是面向契约(contract)编程或者说是面向协议(protocol)编程,就像继承,子类和父类约定好规则,子类然后去实现或者继承,接口刚好满足了这一点,同时它保证了只要实现了该接口的类,其返回的属性都是一致的,这样就保证了其一致性。同时,它避免了单继承的不足,父类中没有提供子类需要的方法(一般来说,父类提供公共的一些方法),不同的子类可以通过实现不同的接口来实现自己独特的方法,这样也保证了子类的差异性。
在继承时,我们要注意里氏代换原则(Liskov Substitution Principle, LSP):
Subtypes must be substitutable for their base types
也就是说子类必须能够替换成它们的基类,更通熟一点的,在一个软件系统中,子类应该可以替换任何基类能够出现的地方,并且经过替换以后,代码还能正常工作。
面向对象的设计关注的是对象的行为,它是使用“行为”来对对象进行分类的,只有行为一致的对象才能抽象出一个类来。我经常说类的继承关系就是一种“Is-A”关系,实际上指的是行为上的“Is-A”关系,可以把它描述为“Act-As”。
里氏代换原则告诉我们类的继承原则:如果一个继承类的对象可能会在基类出现的地方出现运行错误,则该子类不应该从该基类继承,或者说,应该重新设计它们之间的关系。