ISP – 高效的接口
在本节中,我们将探讨一个帮助我们编写有效接口的原则。它被称为 ISP。
ISP 建议我们保持接口小巧,并专注于实现单一职责。所谓小巧的接口,是指在任何单个接口上尽可能少地拥有方法。这些方法都应该与某个共同主题相关。
我们可以看到,这个原则实际上只是 SRP 的另一种形式。我们是在说,一个有效的接口应该描述一个单一职责。它应该涵盖一个抽象,而不是多个。接口上的方法应该紧密相关,并且也与那个单一抽象相关。
如果我们需要更多的抽象,那么我们就使用更多的接口。我们将每个抽象保留在其自己的独立接口中,这就是接口隔离这个术语的来源——我们将不同的抽象分开。
与此相关的 代码异味 是一个覆盖多个不同主题的大型接口。我们可以想象一个接口拥有数百个方法,分成小群组——一些与文件管理相关,一些关于编辑文档,还有一些关于打印文档。这样的接口很快就会变得难以使用。ISP 建议我们通过将接口拆分成几个较小的接口来改进这一点。这种拆分将保留方法群组——因此,你可能会看到用于文件管理、编辑和打印的接口,每个接口下都有相关的方法。通过将这些不同的抽象分开,我们使我们的代码更易于理解。
回顾形状代码中的 ISP 使用
ISP 最显著的应用是在 Shape
接口中,如下所示:
interface Shape {
void draw(Graphics g);
这个接口显然有一个单一的焦点。它是一个非常专注的接口,以至于只需要指定一个方法:draw()
。这里没有其他混合概念引起的混淆,也没有不必要的方法。这个单一方法既是必要的也是充分的。另一个主要例子是在 Graphics
接口中,如下所示:
public interface Graphics {
void drawText(String text);
void drawHorizontalLine(int width);
}
Graphics
接口只包含与在屏幕上绘制图形原语相关的方法。它有两个方法——drawText
用于显示文本字符串,drawHorizontalLine
用于绘制水平方向的线条。由于这些方法紧密相关——技术上称为表现出高内聚性——并且数量少,因此满足了 ISP。这是对图形绘制子系统的有效抽象,根据我们的目的量身定制。
为了完整起见,我们可以以多种方式实现这个接口。GitHub 中的示例使用了一个简单的文本控制台实现:
public class ConsoleGraphics implements Graphics {
@Override
public void drawText(String text) {
print(text);
}
@Override
public void drawHorizontalLine(int width) {
var rowText = new StringBuilder();
for (int i = 0; i < width; i++) {
rowText.append('X');
}
print(rowText.toString());
}
private void print(String text) {
System.out.println(text);
}
}
该实现也符合 LSP——它可以在任何期望 Graphics
接口的地方使用。
ISP
保持接口小巧并与单一想法紧密相关。 |
我们现在已经涵盖了所有五个 SOLID 原则,并展示了它们如何应用于形状代码。它们引导设计朝着紧凑的代码方向发展,具有良好工程结构以帮助未来的维护者。我们知道如何将这些原则融入我们自己的代码中以获得类似的好处。