1.定义
接口隔离原则(Interface Segregation Principle,简称 ISP)是设计模式中的一个原则,它规定客户端不应该依赖它不使用的方法。
换句话说,接口应该被细分为更小的、更具体的接口,以便客户端只依赖于它们需要的方法。
bash
(客户端)
|
V
(公共行为) (特定行为) (特定行为)
+------------+ +-------------+ +-------------+
| I公共接口 | | I特定接口1 | | I特定接口2 |
+------------+ +-------------+ +-------------+
| | |
V V V
+-------+ +---------+ +---------+
| 客户端1 | | 客户端2 | | 客户端3 |
+-------+ +---------+ +---------+
2.内涵
接口应该被细分的足够少,接口与接口直接不应该有依赖,ISP 的目的是提高代码的灵活性、可维护性和可测试性。总结起来,ISP原则的好处和缺点如下。
ISP 的好处
- 提高代码的灵活性
- 提高代码的可维护性
- 提高代码的可测试性
- 减少耦合
- 提高代码的可重用性
ISP 的缺点
- 可能导致更多的接口
- 可能增加代码的复杂性
3.案例对比
例如,以机器类的定义为例,假如一个机器可以打印机,复印机,发传真机,如果不加以深入思考,我们可能如下第一种方式定义。
bad 设计
cpp
//
// Created by Administrator on 2024/5/1.
//
#include <iostream>
using namespace std;
struct Document;
struct IMachine{
virtual void print(Document& doc) = 0;
virtual void scan(Document& doc) = 0;
virtual void fax(Document& doc) = 0;
};
// 接口分离原则
// 发现 MFP 只需要 print 功能即可
// 其他接口到底怎么处理比较好?
struct MFP:IMachine{
void print(Document &doc) override {
// ok
}
void scan(Document &doc) override {
}
void fax(Document &doc) override {
}
};
// 发现scanner 只需要 scan 功能
// 其他接口到底怎么处理比较好?
struct Scanner:IMachine{
void print(Document &doc) override {
// do what?
}
void scan(Document &doc) override {
// ok
}
void fax(Document &doc) override {
// do what?
}
};
如果后面的传真机怎么定义?,除了fax 函数外,其他函数的功能并不是fax 需要的。
好的设计
cpp
#include <iostream>
using namespace std;
struct Document;
// 接口分离
struct IPrinter{
virtual void print(Document& doc) =0 ;
};
struct IScanner {
virtual void scan(Document& doc)=0 ;
};
struct IFax{
virtual void fax(Document& doc)=0 ;
};
struct IMachine:IPrinter,IScanner {
};
struct Machine:IMachine{
IPrinter& printer;
IScanner& scanner;
Machine(IPrinter &printer, IScanner &scanner):printer(printer),scanner(scanner){
}
void print(Document &doc) override {
printer.print(doc);
}
void scan(Document &doc) override {
scanner.scan(doc);
}
};
这种设计,我们可以任意进行组合,组合出不同特性的子类,自由度更高。
4.注意事项
在实际开发中应用接口隔离原则(ISP)时,需要注意以下几点:
- 识别公共行为。第一步是识别客户端共享的公共行为。这些行为可以被抽象到一个公共接口中。
- 细化接口。对于任何不属于公共行为的方法,都应该将其细化到一个单独的接口中。
- 考虑可扩展性。在设计接口时,考虑未来的可扩展性。避免将不相关的功能包含在同一个接口中。
- 使用依赖注入。依赖注入是一种技术,它允许客户端只依赖于它们需要的接口。这有助于实现松耦合和可测试性。
- 避免使用继承。继承是一种实现代码重用的强大机制,但它也可能导致违反 ISP。如果可能的话,优先使用组合而不是继承。
5.最佳实践
注意的具体示例:
- 避免创建上帝接口。上帝接口是一个包含大量方法的大型接口。客户端通常只使用其中一小部分方法,但仍然必须实现所有方法。这违反了 ISP,并可能导致不必要的代码和维护开销。
- 避免使用可选方法。可选方法允许客户端选择性地实现接口中的某些方法。这可能导致代码混乱和不可预测的行为。如果可能的话,避免使用可选方法,而是创建单独的接口来表示可选的行为。
- 考虑未来需求。在设计接口时,考虑未来的需求。避免将不相关的功能包含在同一个接口中,因为这可能会限制接口的未来可扩展性。
6.总结
应用 ISP 时,权衡其优点和缺点非常重要。ISP 可以提高代码的灵活性、可维护性和可测试性,但它也可能导致更多的接口和增加代码的复杂性。
总体而言,ISP 是一个有用的设计原则,它可以帮助提高代码的质量。通过遵循上述准则,开发人员可以有效地应用 ISP,从而获得其好处,同时避免其潜在的缺点。