重构一个接口最好用的原则,没有之一!

Java
206
0
0
2024-02-06

今天我们来聊聊S.O.L.I.D原则中的I:Interface Segregation Principle(ISP),接口隔离原则。

ISP 简介

接口隔离原则(Interface Segregation Principle)的定义是:类间的依赖关系应该建立在最小的接口上。

嗯~~我们从字面上理解下或许更直白一些。所谓“接口隔离”,就是把接口隔离开。接口怎么隔离开呢?当然是接口间相互不影响咯!

接口里面是什么呢?当然封装的是抽象方法,或者我们称为行为。

接口间相互不影响,那就是两个或多个接口中的方法不相互影响。

于是,就是我们经常说到的:一个接口应该拥有尽可能少的行为,使其精简单一。

如果你实在不知道接口隔离原则怎么用?那么最~最~最简单的做法:一个接口里,一个行为方法!

PS:这不一定通用!也不一定完全适合!但它隔离得足够清楚!

大家不是常说:接口的内容一定要尽可能地小,能有多小就多小。我的接口中就只有一个行为方法了,没有更小的啦~😄😄

ISP 示例

接口隔离原则真的足够简单。根据上面对定义的解释,我们可以很快的对下面的示例进行修改。

假设您正在设计一个系统来管理各种类型的设备,例如打印机、扫描仪和传真机。您可能想创建一个名为 Device 的接口,如下所示:

public interface Device
{
    void print();
    void scan();
    void fax();
}

按照我们实际场景中,办公室的电脑都能连接到打印机,进行文件打印。打印机的文件打印行为正好在 Device 接口中有。于是:

public class Printer implements Device {

  @Override
  void print() {
    System.out.println("文件打印!");
  }
  
  @Override
  void scan(){
    System.out.println("文件扫描!");
  }
  
  @Override
  void fax(){
    
  }
}

由于我们实例了 Device 接口,我们就不能只重写 print()和scan()方法。还必须重写 fax() 的方法,即使我们的打印机没有发传真的功能。

同理,简单的传真机,也经常只有发传真的行为。

public class FaxMachine implements Device {

  @Override
  void print() {
    
  }
  
  @Override
  void scan(){
    
  }
  
  @Override
  void fax(){
    System.out.println("传真文件!");
  }
}

我们的 FaxMachine 也重写了全部方法,即使它没有打印和扫描的功能。

可以看出,无论是 Printer 类,还是 FaxMachine 类,它们依赖的行为方法都来之一个比较大的接口 Device。

这违背了我们接口隔离原则的定义:类间的依赖关系应该建立在最小的接口上。

对于 Printer 类来说,最小的接口应该只包含 print() 和 scan() 方法就好;对于 FaxMachine 类来说,最小的接口只需要包含 fax()。

于是,我们就可以基于接口隔离原则对代码进行重构。

先拆出满足 Printer 类的最小接口:

public interface IPrinter
{
    void print();
    void scan();
}

先拆出满足 Printer 类的最小接口:

public interface IFax
{
    void fax();
}

Printer 类和 FaxMachine 类就可以分别实现它们啦~

public class Printer implements IPrinter {

  @Override
  void print() {
    System.out.println("文件打印!");
  }
  
  @Override
  void scan(){
    System.out.println("文件扫描!");
  }
}

原来 Printer 类中不需要的行为方法scan()就可以删除了。

同理,FaxMachine 类也是如此。

public class FaxMachine implements IFax {

  @Override
  void fax(){
    System.out.println("传真文件!");
  }
}

再进一步

我们知道,不是每一个打印机都有打印和扫描功能的。小二哥家里的的打印机,就没有扫描功能。

怎么办?继续拆!

public interface IPrinter
{
    void scan();
}
public interface IScanner
{
    void scan();
}

把扫描功能拆出去后,打印接口 IPrinter 就只有打印功能了。

完美

等等~~ 我们最开始的打印机 Printer 类可是有打印和扫描功能的,你不能给咱们搞丢一个呀!赶紧补起来!

public class Printer implements IPrinter,IScanner  {

  @Override
  void print() {
    System.out.println("文件打印!");
  }
  
  @Override
  void scan(){
    System.out.println("文件扫描!");
  }
}

依赖两个接口(IPrinter 和 IScanner)就好啦~

总结

接口隔离原则,使用起来非常简单方便。它提倡不要将一个大而全的接口扔给使用者,而是将每个使用者关注的接口进行隔离。

换句话说,就是:对于不同的功能的模块分别使用不同接口,而不是使用同一个通用的接口。

如果你在 implements 某个接口的时候,发现该接口中的一些方法用不上,那你就需要对接口进行拆分!

PS:最无脑的操作就是,把接口拆成一个方法对应一个接口!

接口隔离原则,不是无缘无故的存在的。它和之前的单一职责原则和里氏替换原则紧密相关。

一个方法,是不是代表一个行为?单个行为,就是单一的职责。这是不是就体现了单一职责原则。

如果两个对象的行为不一致,它们就不能替换。我们强行把 Printer 类和 FaxMachine 类都定义为 Device 的衍生类,但它们都不能全面提供 Device 拥有的打印、扫描和发传真的功能。

S.O.L.I.D原则中的各个原则间是相互有关联的。具体的关系,我们后面会详细讲解,敬请期待~