Interface Segregation Princeple(ISP 接口隔离原则)
一. What is ISP(Interface Segregation Princeple)
接口隔离原则,旨在确保接口的设计是专门化和细粒度的。根据ISP,一个类不应该被迫依赖于它不使用的方法或属性。
换句话说:宁愿拆成很多小接口,也不要搞一个超级大的胖接口。
二.ISP错误举例
1.1 串口温度传感器
非常臃肿的uart驱动接口,违反ISP原则。
// 超级大接口 - 所有模块都必须实现所有函数
typedef struct {
void (*init)(void* self);
void (*read)(void* self, uint8_t* buf, size_t len);
void (*write)(void* self, const uint8_t* buf, size_t len);
void (*flush)(void* self);
void (*get_status)(void* self);
void (*sleep)(void* self);
void (*wakeup)(void* self);
void (*set_baudrate)(void* self, uint32_t baud);
void (*set_parity)(void* self, uint8_t parity);
void (*ioctl)(void* self, int cmd, void* arg); // 万能控制
} uart_ops; // ← 非常胖的接口
使用时非常尴尬。
// 只想用作只读传感器的模块,却被迫实现一堆无关函数
typedef struct {
uart_ops ops;
// ...
} temperature_sensor;
void temperature_read(void* self, uint8_t* buf, size_t len) { ... }
void temperature_init(void* self) { ... }
// 下面这些传感器根本不需要,却被迫实现
void temperature_write(...) { return; } // 空实现
void temperature_flush(...) { return; }
void temperature_sleep(...) { return; }
void temperature_set_baudrate(...) { return; }
void temperature_ioctl(...) { return; } // 最痛苦的空实现
1.2 机器人
非常臃肿的worker接口,roboter被迫实现所有函数。
typedef struct {
void (*work)(void* self);
void (*eat)(void* self);
void (*sleep)(void* self);
void (*take_break)(void* self);
void (*attend_meeting)(void* self);
} worker; // ← 超级胖
void robot_eat(void* self) { /* 机器人不吃饭 */ }
void robot_sleep(void* self) { /* 机器人不睡觉 */ }
void robot_take_break(...) { /* 无意义 */ }
三.ISP正确举例
3.1 串口温度传感器
划分为应用层和驱动层抽象接口, 按需实现。
// ---------- 温度传感器应用层抽象接口 ----------
typedef struct {
void (*init)(void* sensor);
int (*read_temperature)(void* sensor, float* temp);
void (*deinit)(void* sensor);
} temperature_ops;
// ---------- UART驱动层抽象接口 ----------
typedef struct {
void (*init)(void* uart);
int (*write)(void* uart, const uint8_t* data, size_t len);
int (*read)(void* uart, uint8_t* buf, size_t len);
void (*set_baud)(void* uart, uint32_t baud);
} uart_ops;
3.2 机器人
typedef struct { void (*work)(void*); } IWorkable;
typedef struct { void (*eat)(void*); } IEatable;
typedef struct { void (*sleep)(void*); } ISleepable;
typedef struct { void (*take_break)(void*); } IBreakable;
typedef struct {
IWorkable* workable;
// 人类才有的
IEatable* eatable;
ISleepable* sleepable;
} Human;
typedef struct {
IWorkable* workable;
// 机器人没有其他接口
} Robot;
3.3 函数指针数组
使用函数指针数组实现接口隔离。
typedef enum {
INTERFACE_LIFECYCLE = 0,
INTERFACE_READABLE,
INTERFACE_WRITABLE,
INTERFACE_POWER,
INTERFACE_COUNT
} interface_type_e;
typedef struct {
void* impl;
void* vtable[INTERFACE_COUNT];
} interface_provider;
四.ISP和LSP
ISP的目的是把大的臃肿,功能不相关的接口拆成多个小接口,防止"客户端"被迫实现。
LSP的目的是使用组合、去掉不合理的继承关系。例如正方形继承自矩形,但是改width会连带改height;一般的鸟都可以飞,但是鸵鸟继承鸟不会飞,运行出错。
在实际编码中,“写小接口 + 谨慎使用继承” 是同时满足 ISP 和 LSP 最有效的一招。
浙公网安备 33010602011771号