In Object Oriented Programming, "programming to an interface" is often considered a best practice. Since an interface can have many implementations, the idea is that we should be able to swap out one implementation for another with ease. However, I struggle to understand how to do this when the return type for the interface method is a List.
As an example, say I need to get data about NBA (basketball) players. Two providers of this data could be ESPN and Yahoo. Both ESPN and Yahoo will provide certain fields of a player, like their name
. However, ESPN has information about the player's college
that Yahoo does not. Yahoo might have information about the player's age
, which ESPN does not.
public abstract class Player {
private String name;
}
public class EspnPlayer extends Player {
private String college;
}
public class YahooPlayer extends Player {
private int age;
}
I want to be able to swap between the ESPN and Yahoo providers, so I write an interface with implementations.
Client Code:
public class PlayerController {
public List<Player> getPlayers() {
NbaService nbaService = new EspnServiceImpl(); // or YahooServiceImpl
return nbaService.getPlayers();
}
}
Interface and implementations:
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;
}
}
This does not compile. I thought that this would work because of covariant return types. Now if I change the interface method to List<? extends Player> getPlayers();
, everything compiles. However, I'm not sure if this is a good practice. Can someone help me understand what concepts I'm missing? Thanks.
This does not compile.
You have two options:
List<? extends Player> getPlayers()
in your interface, and now you can write List<EspnPlayer> getPlayers()
which is a valid implementation if this. Note that it is not possible to call .add()
on this list, for proper reasons (after all, adding a non-EspnPlayer to this list would break the list!)interface NbaService<T extends Player> {
public List<T> getPlayers();
}
In Object Oriented Programming, "programming to an interface" is often considered a best practice.
Nice use of the passive voice there, but, as wikipedia editors would say, [citation needed].
There is a grain of truth to this, but as with all programming style suggestions, it's oversimplified. Which isn't a problem, but that's why the following maxim is pretty much always true: If you don't understand the reasons behind a style recommendation, then blindly following it is stupid and will result in worse code. There is no easy out: You must first grok the considerations underlying the recommendation, and until then you just cannot use the recommendation.