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 原则,并展示了它们如何应用于形状代码。它们引导设计朝着紧凑的代码方向发展,具有良好工程结构以帮助未来的维护者。我们知道如何将这些原则融入我们自己的代码中以获得类似的好处。