如何在接口方法中使用带有列表的协变返回类型?

问题描述

在面向对象编程中,“为接口编程”通常被认为是最佳实践。由于一个接口可以有许多实现,我们的想法是我们应该能够轻松地将一个实现替换为另一个实现。 但是,当接口方法的返回类型是 List 时,我很难理解如何执行此操作。

例如,假设我需要获取有关 NBA(篮球)球员的数据。这些数据的两个提供者可能是 ESPN 和雅虎。 ESPN 和雅虎都会提供球员的某些字段,比如他们的 name。但是,ESPN 拥有 Yahoo 没有的有关玩家 college 的信息。雅虎可能有关于球员 age 的信息,而 ESPN 没有。

public abstract class Player {
  private String name;
}
public class EspnPlayer extends Player {
  private String college;
}
public class YahooPlayer extends Player {
  private int age;
}

我希望能够在 ESPN 和 Yahoo 提供商之间进行切换,因此我编写了一个带有实现的接口。
客户端代码

public class PlayerController {
  
  public List<Player> getPlayers() {
    NbaService nbaService = new EspnServiceImpl(); // or YahooServiceImpl
    return nbaService.getPlayers();
  }
}

接口和实现:

public interface NbaService {
  List<Player> getPlayers();
}
public class EspnServiceImpl implements NbaService {

  @Override
  public List<EspnPlayer> getPlayers() {
    List<EspnPlayer> espnPlayers = new ArrayList<>();
    // call ESPN API and get ESPN players
    return espnPlayers;
  }
}

这不会编译。我认为这会起作用,因为协变返回类型。现在,如果我将接口方法更改为 List<? extends Player> getPlayers();,一切都会编译。但是,我不确定这是否是一个好习惯。有人可以帮助我了解我缺少哪些概念吗?谢谢。

解决方法

这不会编译。

您有两个选择:

  • 在您的界面中尝试 List<? extends Player> getPlayers(),现在您可以编写 List<EspnPlayer> getPlayers(),如果这样做,它是一个有效的实现。请注意,出于适当的原因,不可能在此列表中调用 .add()(毕竟,将非 EspnPlayer 添加到此列表会破坏列表!)
  • 如果需要添加这些东西,那么除了拥有之外别无他法:
interface NbaService<T extends Player> {
    public List<T> getPlayers();
}

在面向对象编程中,“为接口编程”通常被认为是最佳实践。

在那里很好地使用被动语态,但是,正如维基百科编辑所说,[需要引用]。

这有一定道理,但与所有编程风格建议一样,它过于简单化了。这不是问题,但这就是为什么下面的格言几乎总是正确的:如果你不理解风格推荐背后的原因,那么盲目遵循它是愚蠢的,会导致更糟糕的代码。没有简单的方法:您必须首先理解推荐背后的考虑因素,然后才能使用推荐。

,

我会做类似的事情:

List<ESPNPlayers> p = getPlayers();

  @Override
  public <T extends Players> List<T> getPlayers() {
    List<T> players = new ArrayList<>();
    // call ESPN API and get ESPN players
    return players;
  }
}

您还可以在类声明中使用泛型类型参数来帮助简化它们。

但是除非您指定要获得哪种类型的玩家,否则您将仅限于 Player 类共有的那些字段。

此外,由于您的子类提供了新的私有字段,因此您需要为它们提供 getter。

最后,对 List 之类的接口进行编程并不意味着您可以只更改列表的内容。这意味着您可以更改列表本身的实现。和往常一样,如果您需要只有实现提供的特定功能(例如方法),您必须返回实现类型而不是接口类型。