【iOS-设计模式】六大设计原则之里氏替换原则(LSP,Lisk

2020-03-24  本文已影响0人  GSNICE

定义

里氏替换原则的定义有两种,据说是由麻省理工的一位姓里的女士所提出,因此以其名进行命名。

定义解读

其实两个定义所表达的意思都相同,就是在所有父类出现的地方,子类都可以出现,并且将父类对象替换为子类对象的时候,程序不会抛出任何异常或者错误,因此我们需要注意的是,尽量不要重载或者重写父类的方法(抽象方法除外),因为这样可能会改变父类原有的行为。

优点

缺点

问题提出

有一功能 P1,由类 A 完成。现需要将功能 P1 进行扩展,扩展后的功能为 P,其中 P 由原有功能 P1 与新功能 P2 组成。功能 P 由类 A 的子类 B 来完成,则子类 B 在完成新功能 P2 的同时,有可能会导致原有功能 P1 发生故障。

解决方案

当使用继承时,遵循里氏替换原则。类 B 继承类 A 时,除添加新的方法完成新增功能 P2 外,尽量不要重写父类 A 的方法,也尽量不要重载父类 A 的方法。

继承包含这样一层含义:父类中凡是已经实现好的方法(相对于抽象方法而言),实际上是在设定一系列的规范和契约,虽然它不强制要求所有的子类必须遵从这些契约,但是如果子类对这些非抽象方法任意修改,就会对整个继承体系造成破坏。而里氏替换原则就是表达了这一层含义。

继承作为面向对象三大特性之一,在给程序设计带来巨大便利的同时,也带来了弊端。比如使用继承会给程序带来侵入性,程序的可移植性降低,增加了对象间的耦合性,如果一个类被其他的类所继承,则当这个类需要修改时,必须考虑到所有的子类,并且父类修改后,所有涉及到子类的功能都有可能会产生故障。

示例

还是继续我们上面的场景:这里,我们将 Employee 类作为父类,它里面有一个计算工资的方法 calculateSalary,代码如下所示:
.h

@interface Employee : NSObject

// 计算工资
- (void)calculateSalary:(NSString *)name;

@end

.m

#import "Employee.h"

@implementation Employee

- (void)calculateSalary:(NSString *)name
{
    NSLog(@"%@的工资是:100",name);
}

@end
调用代码
// 调用Employee类的calculateSalary方法
Employee *employee = [[Employee alloc] init];
[employee calculateSalary:@"张三"];
输出结果
张三的工资是:100

现在我们为 Employee 类添加一个子类总监 Director 类,该类新增了一个职责说明的方法和重写了 calculateSalary 方法,如下所示:

#import "Director.h"

@implementation Director

- (void)calculateSalary:(NSString *)name
{
    NSLog(@"总监%@的工资是:10000",name);
}

- (void)duty
{
    NSLog(@"总监的职责是管理");
}

@end
调用代码
Employee *employee = [[Director alloc] init];  
// 将所有父类出现的地方都替换成子类
[employee calculateSalary:@"张三"];
输出结果
总监张三的工资是:10000

从上面的结果我们可以看到,由于重写了父类 Employee 的 calculateSalary 方法,造成计算薪资的方法都是调用子类 Director 重写后的方法。如果应用场景是要求公司的薪资都是统一的,那么调用 Director 类重写的方法就是不正确的。如果非要重写父类里面的方法,比较通用的做法是:原来的父类和子类都继承一个更通用的基类,原有的继承关系去掉,采用依赖、聚合、组合等关系代替。

里氏替换原则通俗的来讲就是:子类可以扩展父类的功能,但不能改变父类原有的功能。它包含以下2层含义:

在项目中所有使用子类的地方都可用父类替换,但在调用方法的时候,即呈现面向对象编程的多态性。里氏替换原则,是非常重要的原则,也是相对较难的原则。

上一篇下一篇

猜你喜欢

热点阅读