Generics: Defining generic classes
What we’ll cover
Non-Generic vs Generic Structure
Abstracting a Generic Structure
Defining generic classes
- You know to define a generic class when classes share signatures but have variable Type
Defining a non-Generic Structure
public class BlackJackGameEngine {
private BlackJackGame game;
private List<BlackJackPlayer> players;
public BlackJackGameEngine(BlackJackGame game, List<BlackJackPlayer> players) {
this.game = game;
this.players = players;
}
public void start() {
game.addPlayers(players);
while(!game.isOver()) {
game.evaluateTurns(players);
}
}
}
Defining a non-Generic Structure
public class GoFishGameEngine {
private GoFishGame game;
private List<GoFishPlayer> players;
public GoFishGameEngine(GoFishGame game, List<GoFishPlayer> players) {
this.game = game;
this.players = players;
}
public void start() {
game.addPlayers(players);
while(!game.isOver()) {
game.evaluateTurns(players);
}
}
}
Abstracting a Generic Structure
- To abstract each of the previously mentioned classes we must first ensure each variable type conforms to a common base-type, or interface
- Variable types in this example include:
- GoFishPlayer
- GoFishGame
- GoFishGameEngine
- BlackJackPlayer
- BlackJackGame
- BlackJackGameEngine
Abstracting Generic Structures
Player Interface
- Ensuring
GoFishPlayer
andBlackJackPlayer
conform to a commonPlayer
interface
public interface Player {
void play();
}
public class GoFishPlayer implements Player {
// definition omitted for brevity
}
public class BlackJackPlayer implements Player {
// definition omitted for brevity
}
Abstracting Generic structures
Game Interface
- Ensuring
GoFishGame
andBlackJackGame
conform to a commonGame
interface
public interface Game {
void evaluateTurn(Player player);
void addPlayer(Player player);
Boolean isOver();
}
public class GoFishGame implements Game {
// definition omitted for brevity
}
public class BlackJackGame implements Game {
// definition omitted for brevity
}
Noticing Design Flaw
- Notice that the
Game
interface enforces mediation of aPlayer
but ignores specific player-type. - This design flaw allows for strange orientation
public void demo() {
Player player = new BlackJackPlayer();
Game game = new GoFishGame();
// no exception thrown; should be impossible behavior
game.addPlayer(player);
}
Parameterizing Game Interface
- A specific player-type can be enforced by parameterizing
Game
interfacepublic interface Game<PlayerType extends Player> { void evaluateTurn(PlayerType player); void addPlayer(PlayerType player); void addPlayers(Iterable<? extends PlayerType> player); Boolean isOver(); }
Parameterizing GoFish and BlackJack
- It follows that the implementing classes should be parameterized respectively
public class GoFishGame implements Game<GoFishPlayer> {
// definition omitted for brevity
}
public class BlackJackGame implements Game<BlackJackPlayer> {
// definition omitted for brevity
}
Noticing Design Advantage
Part 1
- Notice that the
Game
interface refuses mediation of invalidPlayer
-types
public void demo() {
Player player = new BlackJackPlayer();
Game<GoFishPlayer> game = new GoFishGame();
game.addPlayer(player); // compile-time exception
}
Noticing Design Advantage
Part 2
- Notice that the
Game
interface refuses mediation of invalidPlayer
-types
public void demo() {
Player player = new GoFishPlayer();
Game<BlackJackPlayer> game = new BlackJackGame();
game.addPlayer(player); // compile-time exception
}
Noticing Design Advantage
Part 3
- Notice that the
Game
interface enforces validPlayer
-types
public void demo() {
GoFishPlayer player = new GoFishPlayer();
Game<GoFishPlayer> game = new GoFishGame();
game.addPlayer(player); // valid
}
Abstracting Generic structures
Game Engine Interface
- Creating
GameEngineInterface
forGoFishGameEngine
andBlackJackGameEngine
to conform to.
public interface GameEngineInterface {
void start();
Game getGame();
Iterable<Player> getPlayers();
}
Abstracting Generic Structures
Game Engine Interface
- Defining abstract
GameEngine
public abstract class GameEngine implements GameEngineInterface {
private Game game;
private List<Player> players;
public GameEngine(Game game, List<Player> players) {
this.game = game;
this.players = players;
}
public void start() {
game.addPlayers(players);
while(!game.isOver()) {
game.evaluateTurns(players);
}
}
}
Abstracting Generic Structures
Parameterizing Game Engine Interface
- Parameterizing abstract
GameEngineInterface
public interface GameEngineInterface<
PlayerType extends Player,
GameType extends Game<PlayerType>> {
void start();
GameType getGame();
Iterable<PlayerType> getPlayers();
}
Abstracting Generic Structures
Parameterizing Game Engine Class
- It follows that implementing classes should be parameterized respectively
public abstract class GameEngine<
PlayerType extends Player,
GameType extends Game<PlayerType>>
implements GameEngineInterface<PlayerType, GameType> {
private GameType game;
private List<PlayerType> players;
public GameEngine(GameType game, List<PlayerType> players) {
this.game = game;
this.players = players;
}
public void start() {
// definition omitted
}
}
Abstracting Generic Structures
- Parameterizing
GoFishGameEngine
public class GoFishGameEngine extends GameEngine<GoFishPlayer, GoFishGame> {
public GoFishGameEngine(GoFishGame game, List<GoFishPlayer> players){
super(game, players);
}
}
Former Definition
GoFishGameEngine
public class GoFishGameEngine {
private GoFishGame game;
private List<GoFishPlayer> players;
public GoFishGameEngine(GoFishGame game, List<GoFishPlayer> players) {
this.game = game;
this.players = players;
}
public void start() {
game.addPlayers(players);
while(!game.isOver()) {
game.evaluateTurns(players);
}
}
}
Abstracting Generic Structures
- Parameterizing
BlackJackGameEngine
public class BlackJackGameEngine extends GameEngine<BlackJackPlayer, BlackJackGame> {
public BlackJackGameEngine(BlackJackGame game, List<BlackJackPlayer> players){
super(game, players);
}
}
Former Definition
BlackJackGameEngine
public class BlackJackGameEngine {
private BlackJackGame game;
private List<BlackJackPlayer> players;
public BlackJackGameEngine(BlackJackGame game, List<BlackJackPlayer> players) {
this.game = game;
this.players = players;
}
public void start() {
game.addPlayers(players);
while(!game.isOver()) {
game.evaluateTurns(players);
}
}
}