问题描述
在面向对象编程中,“为接口编程”通常被认为是最佳实践。由于一个接口可以有许多实现,我们的想法是我们应该能够轻松地将一个实现替换为另一个实现。 但是,当接口方法的返回类型是 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
之类的接口进行编程并不意味着您可以只更改列表的内容。这意味着您可以更改列表本身的实现。和往常一样,如果您需要只有实现提供的特定功能(例如方法),您必须返回实现类型而不是接口类型。