找回密码
 立即注册
首页 业界区 安全 Liskov Substitution Principle(LSP 里氏替换原则) ...

Liskov Substitution Principle(LSP 里氏替换原则)

粉押淫 2026-2-23 20:20:00
一.what is Liskov Substitution Principle

里氏替换原则是面向对象设计中的五大SOLID原则之一,是开闭原则的重要补充。它规定了子类可以扩展基类的功能,但不能改变基类原有的行为。

二.LSP的设计准则

LSP强调了继承不仅是“is a”的关系,并且行为上必须是“is-substitutable-for”(可以完全替换)的关系。

  • 前置条件(Preconditions):子类方法的前置条件不能比基类更严格(参数范围要更宽松或相等)。
  • 后置条件(Postconditions):子类方法的后置条件不能比基类更宽松(返回值范围要更严格或相等)。
  • 不变式(Invariants):子类必须维护基类定义的不变式。
  • 异常(Exceptions):子类方法不能抛出比基类更多的异常类型。

三.举例

3.1 基类鸟和和子类鸵鸟

语义上:鸵鸟是一种鸟,正确。
行为上:如果基类Bird有fly()方法,正常鸟可以飞,但鸵鸟不能飞,或者执行飞了会抛异常(子类方法变得更严格)。这时用鸵鸟替换Bird,就会破坏程序原有行为, 违反LSP。

3.2 linux虚拟文件系统

vfs.h
点击查看代码
  1. #ifndef VFS_H
  2. #define VFS_H
  3. #include <stdio.h>
  4. #include <stddef.h>
  5. // 1. 定义操作契约 (类似 C++ 的纯虚类)
  6. struct file_operations {
  7.     int (*open)(struct my_file *file);
  8.     ssize_t (*read)(struct my_file *file, char *buf, size_t count);
  9. };
  10. // 2. 定义通用对象
  11. struct my_file {
  12.     const struct file_operations *f_ops; // 父类接口指针
  13.     void *private_data;                  // 指向具体的“子类”数据
  14. };
  15. // 3. 通用高层接口:无论什么文件,调用者只用这两个函数
  16. void vfs_open(struct my_file *file);
  17. void vfs_read(struct my_file *file, char *buf, size_t count);
  18. #endif
复制代码
vfs.c点击查看代码
  1. #include "vfs.h"
  2. void vfs_open(struct my_file *file) {
  3.     if (file->f_ops->open) {
  4.         file->f_ops->open(file);
  5.     }
  6. }
  7. void vfs_read(struct my_file *file, char *buf, size_t count) {
  8.     if (file->f_ops->read) {
  9.         file->f_ops->read(file, buf, count);
  10.     }
  11. }
复制代码
实现磁盘设备和键盘设备驱动

driver.c
点击查看代码
  1. #include "vfs.h"
  2. #include <stdlib.h>
  3. // --- 子类 A: 磁盘文件 ---
  4. struct disk_file {
  5.     const char *disk_path;
  6.     int sector_id;
  7.     struct my_file vfs_node; // 嵌入父类对象
  8. };
  9. static int disk_open(struct my_file *f) {
  10.     printf("[Disk] 寻道到起始扇区...\n");
  11.     return 0;
  12. }
  13. static ssize_t disk_read(struct my_file *f, char *b, size_t c) {
  14.     printf("[Disk] 从物理磁盘读取 %zu 字节数据\n", c);
  15.     return c;
  16. }
  17. const struct file_operations disk_fops = { .open = disk_open, .read = disk_read };
  18. // --- 子类 B: 键盘设备 ---
  19. struct keyboard_dev {
  20.     int irq_line;
  21.     struct my_file vfs_node;
  22. };
  23. static int kbd_open(struct my_file *f) {
  24.     printf("[KBD] 激活中断线,准备监听按键...\n");
  25.     return 0;
  26. }
  27. static ssize_t kbd_read(struct my_file *f, char *b, size_t c) {
  28.     printf("[KBD] 从缓冲区获取击键数据...\n");
  29.     return c;
  30. }
  31. const struct file_operations kbd_fops = { .open = kbd_open, .read = kbd_read };
复制代码
main.c点击查看代码
  1. #include "vfs.h"
  2. // 外部驱动定义的 ops
  3. extern const struct file_operations disk_fops;
  4. extern const struct file_operations kbd_fops;
  5. int main() {
  6.     char buffer[128];
  7.     // 1. 创建磁盘文件对象
  8.     struct my_file file_a = { .f_ops = &disk_fops };
  9.    
  10.     // 2. 创建键盘设备对象
  11.     struct my_file file_b = { .f_ops = &kbd_fops };
  12.     // 3. 里氏替换原则的体现:
  13.     // vfs_read 接受的是 struct my_file*。
  14.     // 无论底层是磁盘还是键盘,对于 vfs_read 来说都是一样的。
  15.     printf("--- 操作文件 A ---\n");
  16.     vfs_open(&file_a);
  17.     vfs_read(&file_a, buffer, 100);
  18.     printf("\n--- 操作文件 B ---\n");
  19.     vfs_open(&file_b);
  20.     vfs_read(&file_b, buffer, 100);
  21.     return 0;
  22. }
复制代码

这个例子满足里氏替换原则:

  • 行为一致性:vfs_read 预期调用 read 后会得到数据。disk_read 和 kbd_read 虽然获取数据的手段不同(一个读磁道,一个等中断),但结果都符合“填充缓冲区并返回”的契约。
  • 完全替换:在 main.c 中,我们可以把 file_a 换成 file_b 而不需要修改 vfs_read 的任何一行代码。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

6 天前

举报

您需要登录后才可以回帖 登录 | 立即注册