第六章:接口隔离原则——接口要小而专

接口隔离原则,英文叫 Interface Segregation Principle,简称 ISP。它是 SOLID 五大原则之一。说白了就是一句话:客户端不应该被迫依赖它不需要的接口

我刚开始接触这个原则时,觉得它不就是「接口要小一点」嘛,有啥好讲的?直到我在一个老项目里接手了一个超级接口,才真正体会到什么叫「胖接口的噩梦」。嗯,今天我们就来好好聊聊这个。

胖接口到底有多「胖」?

先看一个反面教材。假设我们有一个多功能打印机接口:

public interface MultiFunctionPrinter {
    void print(String content);
    void scan(String path);
    void fax(String number);
    void copy(String source, String target);
    void staple();  // 装订
    void duplexPrint(String content);  // 双面打印
}

这个接口看起来挺全的,对吧?但问题来了——不是所有打印机都支持所有功能

比如一个简单的喷墨打印机,它只能打印,不支持扫描、传真、装订。但你强迫它实现这些方法,怎么办?

public class SimplePrinter implements MultiFunctionPrinter {
    @Override
    public void print(String content) {
        System.out.println("打印:" + content);
    }

    @Override
    public void scan(String path) {
        throw new UnsupportedOperationException("不支持扫描");
    }

    @Override
    public void fax(String number) {
        throw new UnsupportedOperationException("不支持传真");
    }

    // ... 其他方法全部抛异常
}

我在项目中见过这种代码,真的。一个接口十几个方法,实现类里一半都是 throw new UnsupportedOperationException()。你想想看,调用方拿到这个对象,根本不知道哪些方法能用,哪些不能用。只能靠猜,或者看文档——但文档往往过时了。

⚠️ 胖接口的三大危害:
  • 强迫实现不需要的行为:子类被迫实现一堆无用方法,代码里全是异常或空实现
  • 接口变更影响面大:你加一个方法,所有实现类都要改,哪怕它们根本不需要
  • 调用方困惑:客户端不知道哪些方法可以安全调用,容易引发运行时异常

接口隔离原则的核心思想

接口隔离原则告诉我们:接口要小而专,一个接口只服务于一个特定的客户端或一组紧密相关的行为

怎么理解?我个人的习惯是:接口的方法数量控制在 3-5 个以内。如果一个接口的方法超过 7 个,我就会怀疑它是不是太「胖」了。

但注意,不是说方法少就一定好。关键是内聚性——接口里的方法应该属于同一个职责范畴。

实战:拆分多功能接口

好,我们拿上面的多功能打印机来开刀。怎么拆?

先分析一下,这些功能其实可以分成几个独立的职责:

  • 打印功能:print, duplexPrint
  • 扫描功能:scan
  • 传真功能:fax
  • 复印功能:copy
  • 装订功能:staple

每个职责拆成一个独立的接口:

public interface Printer {
    void print(String content);
}

public interface Scanner {
    void scan(String path);
}

public interface Fax {
    void fax(String number);
}

public interface Copier {
    void copy(String source, String target);
}

public interface Stapler {
    void staple();
}

public interface DuplexPrinter {
    void duplexPrint(String content);
}

现在,一个简单的打印机只需要实现它需要的接口:

public class SimplePrinter implements Printer {
    @Override
    public void print(String content) {
        System.out.println("打印:" + content);
    }
}

一个高端多功能一体机可以实现多个接口:

public class HighEndPrinter implements Printer, Scanner, Fax, Copier, Stapler, DuplexPrinter {
    @Override
    public void print(String content) {
        System.out.println("高速打印:" + content);
    }

    @Override
    public void scan(String path) {
        System.out.println("高清扫描到:" + path);
    }

    @Override
    public void fax(String number) {
        System.out.println("发送传真至:" + number);
    }

    @Override
    public void copy(String source, String target) {
        System.out.println("复印:" + source + " -> " + target);
    }

    @Override
    public void staple() {
        System.out.println("装订完成");
    }

    @Override
    public void duplexPrint(String content) {
        System.out.println("双面打印:" + content);
    }
}

你看,这样每个实现类只实现它真正需要的接口。调用方也清楚——你拿到一个 Printer 类型的引用,就知道它肯定能打印,不会抛异常。

💡 我的经验: 我曾经在一个支付系统中,把「支付接口」拆成了「支付」「退款」「查询」「对账」四个小接口。结果每个模块只依赖自己需要的接口,改动一个不影响其他。后来加了个「分期支付」功能,只需要新增一个接口,完全不影响现有代码。

接口隔离 vs 单一职责

有人会问:接口隔离原则和单一职责原则(SRP)有什么区别?

嗯,这个问题很好。我简单说一下:

  • 单一职责原则:关注的是的职责,一个类只应该有一个引起它变化的原因
  • 接口隔离原则:关注的是接口的设计,接口应该小而专,客户端不应该依赖它不需要的方法

说白了,SRP 是「类要专一」,ISP 是「接口要小巧」。两者相辅相成,但侧重点不同。

如何判断接口是否「胖」了?

我总结了几个判断标准,你可以对照着检查:

判断标准 说明
方法数量 超过 7 个方法,大概率需要拆分
实现类中是否有空实现或抛异常 如果有,说明接口太胖了
调用方是否只用了部分方法 如果调用方只用了 20% 的方法,接口可能太胖
接口变更时是否影响多个不相关的实现类 如果加一个方法导致多个无关类修改,接口太胖
🔑 核心原则: 接口是给客户端用的,不是给实现类用的。设计接口时,站在调用方的角度思考——「这个客户端需要什么?」而不是「这个实现类能做什么?」

接口隔离的 SVG 结构图

下面这张图展示了胖接口拆分前后的对比:

接口隔离原则:胖接口 vs 小接口 ❌ 胖接口 MultiFunctionPrinter + print() + scan() + fax() + copy() + staple() + duplexPrint() SimplePrinter(抛异常) HighEndPrinter(全实现) ✅ 小接口 Printer + print() Scanner + scan() Fax + fax() Copier + copy() Stapler + staple() DuplexPrinter + duplexPrint() SimplePrinter 只实现 Printer HighEndPrinter 实现所有接口

避坑指南

我曾经在一个项目中,把接口拆得太细了。一个订单系统,我拆了十几个接口,每个接口就一两个方法。结果呢?类爆炸了,调用方要同时依赖五六个接口,反而更复杂。

所以,接口隔离不是「越细越好」。我建议你把握一个度:

  • 接口的粒度应该与客户端的视角一致。如果客户端总是同时使用打印和扫描,那把它们放在一个接口里也没问题
  • 不要为了隔离而隔离。如果一个接口目前只有一个实现类,而且短期内不会有变化,那保持现状就好
  • 重构比设计更重要。我很少一开始就把接口设计得完美无缺。通常是先写一个接口,等发现它变「胖」了,再拆分。代码是活的,允许它慢慢变好
📌 总结一下:
  • 胖接口的典型症状:实现类抛异常、调用方困惑、变更影响面大
  • 接口隔离的核心:小而专,一个接口只服务一个客户端
  • 拆分方法:按职责拆成多个小接口,实现类按需实现
  • 注意度:不是越细越好,要结合实际情况

好了,接口隔离原则就聊到这里。记住一句话:接口是契约,契约越简单,越不容易出错


公众号:蓝海资料掘金营,微信 deep3321