目录
一、什么是里氏替换原则?
里氏替换原则,英文叫Liskov Substitution Principle,简称LSP(老色皮,哈哈)。里氏替换原则,其实是没有我们前面,说的SRP和OCP比较见名知意一些。根据他们两个的中文名称,我们都很容易联想到他的定义。比如,单一职责原则,就是一个类或者模块只负责一个职责。而开闭原则,根据名字,我们也不难得知其定义就是,对扩展开发,对修改关闭。然而,里氏替换原则,根据名称,我们却难以得知其到底有何含义。
里氏替换,其实重点就在后面的替换两字。我们来看里氏替换的一般定义:“派生类(子类)对象可以在程序中代替其基类(超类)对象”。也就是说,在父类对象出现的地方,子类对象都可以进行替换,并且这个替换是要保证原来的逻辑行为不变以及正确性不变。所以,在结合这个名字,里氏替换,好像也不难那么理解了。里氏替换,谐音"理是替换"。也就是子类对象替换父类对象一定是要符合道理的,而这个道理就是上述说的,保证逻辑行为以及正确性不变。
二、里氏替换原则的应用
比如有这样一个场景,我们需要开发一个自定义系统监控探针,这个自定义监控的探针可以监控linux、windows、aix等操作系统。这个核心逻辑的步骤大致就是如下两步:
1、使用对应操作系统命令获取指标信息。
2、将获取的指标信息进行封装处理,比如将获取的指标信息按照某一个格式封装处理然后推送到某个统一监控的中间组件上等。
显而易见,第一步的获取操作系统指标是变化的,而第二步封装处理等操作,我们暂且当作不管任何操作系统都是一样的处理。那么此时,我们就可以使用里氏替换原则,来使得第二步的代码具有更高的灵活性、复用性等。
比如,针对第一步,我们可以先定义一个如下接口:
public interface SystemMonitorMetricDao {
Object queryCpuUtilizationInfo();
Object queryCpuCoreNumInfo();
SystemDiskMetricInfo queryDiskInfo();
SystemBaseMetricInfo queryMemoryInfo();
}
接着,我们定义三个具体的实现类,它们分别针对Linux、Windows和AIX系统,负责获取各自的监控指标。
aix系统指标获取实现类如下:
public class AixSystemMonitorMetricImpl implements SystemMonitorMetric {
@Override
public Object queryCpuUtilizationInfo() {
Double cpu = Double.valueOf(0);
//省略具体逻辑代码
..........
return cpu;
}
@Override
public Object queryCpuCoreNumInfo() {
Integer cpuCore = 0;
//省略具体逻辑代码
..........
return cpuCore;
}
@Override
public SystemDiskMetricInfo queryDiskInfo() {
SystemDiskMetricInfo systemDiskMetricInfo = new SystemDiskMetricInfo();
//省略具体逻辑代码
..........
return systemDiskMetricInfo;
}
@Override
public SystemBaseMetricInfo queryMemoryInfo() {
SystemBaseMetricInfo systemBaseMetricInfo = new SystemBaseMetricInfo();
//省略具体逻辑代码
..........
return systemBaseMetricInfo;
}
}
linux系统指标获取实现类如下:
public class LinuxSystemMonitorMetricImpl implements SystemMonitorMetric {
@Override
public Object queryCpuUtilizationInfo() {
Double cpu = Double.valueOf(0);
//省略具体逻辑代码
..........
return cpu;
}
@Override
public Object queryCpuCoreNumInfo() {
Integer cpuCore = 0;
//省略具体逻辑代码
..........
return cpuCore;
}
@Override
public SystemDiskMetricInfo queryDiskInfo() {
SystemDiskMetricInfo systemDiskMetricInfo = new SystemDiskMetricInfo();
//省略具体逻辑代码
..........
return systemDiskMetricInfo;
}
@Override
public SystemBaseMetricInfo queryMemoryInfo() {
SystemBaseMetricInfo systemBaseMetricInfo = new SystemBaseMetricInfo();
//省略具体逻辑代码
..........
return systemBaseMetricInfo;
}
}
windwos系统指标获取实现类如下:
public class WindowsSystemMonitorMetricImpl implements SystemMonitorMetric {
@Override
public Object queryCpuUtilizationInfo() {
Double cpu = Double.valueOf(0);
//省略具体逻辑代码
..........
return cpu;
}
@Override
public Object queryCpuCoreNumInfo() {
Integer cpuCore = 0;
//省略具体逻辑代码
..........
return cpuCore;
}
@Override
public SystemDiskMetricInfo queryDiskInfo() {
SystemDiskMetricInfo systemDiskMetricInfo = new SystemDiskMetricInfo();
//省略具体逻辑代码
..........
return systemDiskMetricInfo;
}
@Override
public SystemBaseMetricInfo queryMemoryInfo() {
SystemBaseMetricInfo systemBaseMetricInfo = new SystemBaseMetricInfo();
//省略具体逻辑代码
..........
return systemBaseMetricInfo;
}
}
第二步的实现大概就如下:
public class SystemMonitorService {
public void systemMonitor(SystemMonitorMetric systemMonitorMetric) {
IMetricQueryService metricQueryService = BeanFactory.getMetricQueryService();
//根据传入的具体实现类获取指标数据
List<PushMetricToPrometheusParam> paramList = metricQueryService.queryMetricInfo(systemMonitorMetric);
if (paramList == null || paramList.size() == 0) return;
//发送指标到pushGateway
sendMetricsToPushGateway(paramList);
}
里氏替换:
public static void main(String[] args) {
// 创建 SystemMonitorService 和 AixSystemMonitorMetricImpl对象
SystemMonitorService systemMonitorService = new SystemMonitorService();
SystemMonitorMetric systemMonitorMetric = new AixSystemMonitorMetricImpl();
//调用系统监控方法,监控不同得系统就传入不同得具体实现,这里监控得aix系统,所以就传入得aix系统指标获取得实现类
systemMonitorService.systemMonitor(systemMonitorMetric);
}
在上面的代码设计中,子类AixSystemMonitorMetricImpl、LinuxSystemMonitorMetricImpl以及WindowsSystemMonitorMetricImpl的设计完全符合里式替换原则,可以替换父类出现的任何位置,并且原来代码的逻辑行为不变且正确性也没有被破坏。
那么,此时可能会有人说了,这里氏替换不就是多态吗?
其实不然。多态是面向对象编程的一个特点,也是编程语言里的一种用法。它让我们知道怎么实现代码。里氏替换是个设计规矩,告诉我们怎么设计继承里的子类。子类要能代替父类,但不能改原来程序的逻辑,也不能让程序出错。就是说,里氏替换在多态的基础上,多了个不让程序逻辑和正确性变的要求。
三、不符合里氏替换原则的情况
1、子类违背父类声明要实现的功能
2、子类违背父类对输入、输出、异常的约定
3、子类违背父类注释中所罗列的任何特殊说明