面向对象语言都有继承的特性,里氏替换原则要求,任何基类出现的地方,都可以将该基类替换为其子类,意思就是说无论在任何地方,子类都可以接替父类来使用。
下面举个符合里氏替换原则的例子。我们声明一个Bird
父类,它只有一个方法fly
,然后我们继承Bird
生成两个子类,分别是Swallow
(燕子)和Eagle
(老鹰):
Bird父类:
@interface Bird : NSObject
- (void)fly;
@end
@implementation Bird
- (void)fly{
//Bird为抽象类,fly方法由子类实现
}
@end
Swallow类:
@interface Swallow : Bird
@end
@implementation Swallow
- (void)fly{
NSLog(@"燕子飞");
}
@end
Eagle类:
@interface Eagle : Bird
@end
@implementation Swallow
- (void)fly{
NSLog(@"老鹰飞");
}
@end
接着就可以让我们的鸟飞一飞了:
Bird *aBird = [Bird new];
[aBird fly];
根据里氏替换原则:任何基类出现的地方,都可以将该基类替换为其子类,以上场景也可以替换为:
Bird *aBird = [Swallow new];
[aBird fly];
也可以替换为:
Bird *aBird = [Eagle new];
[aBird fly];
无论是将Bird
替换为Swallow
还是Eagle
,完全没有问题。这就是里氏替换原则,非常简单,所以就算你之前没有听说过这个原则,相信你大部分的代码都是遵守这个原则的。
不过如果你没有意识去坚持这个原则,就可能会犯错误。下面举个不符合里氏替换原则的例子。比方说我们现在需要设计一个Chicken
(鸡)类,鸡能不能算是一种鸟呢?鸡和鸟其实有很多类似之处的,例如:浑身都是羽毛,有爪子,能下蛋等,假设我们上面的Bird
除了fly
方法之外,也包含了这些属性。为了方便,我们就让Chicken
类继承Bird
。因为鸡不会飞,所以我们不实现fly
方法:
@interface Chicken : Bird
//浑身都是羽毛,有爪子,能下蛋。。。
@end
@implementation Chicken
//实现除了“fly”之外的其他方法
@end
根据里氏替换原则,我们是可以让我们的鸡也飞一下的:
Bird *aBird = [Chicken new];
[aBird fly];
Chicken
没有实现fly
方法,所以该代码什么也不会发生。这好像也没什么问题,但是如果上述场景是把创建的鸟从十层楼上扔下去让它飞呢?想想我们可怜的的鸡的下场吧。所以Chicken
在这里不能替换Bird
,也就是不符合里氏替换原则。
总结
面向对象语言的继承特性,类似于现实生活中的继承,但又有很大区别。在现实生活中,子类并不是完全继承父类的属性,以父子关系为例,其实儿子很多特性并没有继承父亲的,要不很多父亲也不会对儿子抱怨“你有我的一半聪明就好了”。但是对于面向对象语言的继承,里氏替换原则要求子类必须完全实现父类的方法,否则就可能出现上述将鸡活活摔死的问题。
此外,对于支持方法重载的语言(例如JAVA
),如果在子类中重载了父类的方法,那么里氏替换原则要求:
- 重载方法的输入参数,必须与父类中方法的输入参数相同或者更宽松;
- 重载方法的输出结果,必须与父类中方法的输出结果相同或者更狭窄。
由于Objective-C
并不支持方法重载,对于上述两个要求就不细说了。
继承是面向对象语言中非常优秀的语言机制,里氏替换原则规范了继承的用法,避免使用继承可能出现的问题。
网友评论