Avoid raw type warnings with a factory that returns a generic object implementing a strategy pattern

428 Views Asked by At

My goal is to have an abstract Game class, whose constructor accepts a GameEngine that is suitable for derived Game classes. This abstract Game class will implement generic methods that are suitable for all Game implementations. You can think of the GameEngine as fulfilling a strategy pattern, to which Game delegates methods.

Therefore, upon creating a Game with a GameFactory, I don't care what implementation of Game the factory returns. I just want to make sure that the Game implementation gets constructed with the appropriate GameEngine implementation. However, if I simply return the raw type Game from the factory, I of course get Raw use of parameterized class 'Game' warnings.

Furthermore, ideally, the GameFactory.createGame(settings) method should not have to pass a type, but simply infer the type based on some property of settings.

This is the gist of the code I have:

public abstract class GameEngine<T extends Game<T>> {

  public void doSomethingAllEnginesUnderstand(T game);
}

public class LocalGameEngine
    extends GameEngine<LocalGame> {
}

public class RemoteGameEngine
    extends GameEngine<RemoteGame> {
}

public abstract class Game<T extends Game<T>> {

    private final GameEngine<T> gameEngine;

    protected Game(GameEngine<T> gameEngine) {
        this.gameEngine = gameEngine;
    }

    protected abstract T getSelf();
    
    public final void doSomethingAllEnginesUnderstand() {
      gameEngine.doSomethingAllEnginesUnderstand(getSelf());
    }
}

public class LocalGame 
    extends Game<LocalGame> {

    public LocalGame(LocalGameEngine gameEngine) {
        super(gameEngine);
    }

    @Override
    protected LocalGame getSelf() {
        return this;
    }
}

public class RemoteGame 
    extends Game<RemoteGame> {

    public RemoteGame(RemoteGameEngine gameEngine) {
        super(gameEngine);
    }

    @Override
    protected RemoteGame getSelf() {
        return this;
    }
}

public class GameFactory {

  // returns raw type Game
  public Game createGame(GameSettings settings) {
    if(settings.totalPlayers() > 1) {
      return new RemoteGame(new RemoteGameEngine());
    }
    else {
      return new LocalGame(new LocalGameEngine());
    }
  }
}

Am I misusing/misunderstanding generics to reach my stated goal? Is it possible to not make Game a generics class, while still mandating that an appropriate GameEngine implementation is passed to the constructor?

1

There are 1 best solutions below

3
Nikolas Charalambidis On

I feel you overuse the generics and I find Game<T extends Game<T>> an overkill. I'd design it in this way:

  • Make LocalGame and RemoteGame extends Game (no generic type). Use polymorphysm when you @Override the getSelf method:

    public class LocalGame extends Game {
    
        public LocalGame(LocalGameEngine gameEngine) {
             super(gameEngine);
        }
    
        @Override
        protected LocalGame getSelf() {
            return this;
        }
    }
    
    public class RemoteGame extends Game {
    
         public RemoteGame(RemoteGameEngine gameEngine) {
             super(gameEngine);
         }
    
         @Override
         protected RemoteGame getSelf() {
             return this;
         }
    }
    
  • Make LocalGameEngine and RemoteGameEngine extends GameEngine (no generic type)

    // actuyally, this abstract class can be an interface instead
    public abstract class GameEngine {
        public void doSomethingAllEnginesUnderstand(Game game) {}
    }
    
    public class LocalGameEngine extends GameEngine { }
    
    public class RemoteGameEngine extends GameEngine { }
    
  • Make Game require any subclass of GameEngine, the generic part can be used in the constructor only, you don't need to make the whole Game generic (Game<T>).

    public abstract static class Game {
    
        private final GameEngine gameEngine;
    
        protected <T extends GameEngine> Game(T gameEngine) {
            this.gameEngine = gameEngine;
        }
    
        protected abstract Game getSelf();
    
        public final void doSomethingAllEnginesUnderstand() {
            gameEngine.doSomethingAllEnginesUnderstand(getSelf());
        }
    }
    
  • The whole GameFactory becomes simplified:

    public Game createGame(GameSettings settings) {
        if (settings.totalPlayers() > 1) {
                return new RemoteGame(new RemoteGameEngine());
        } else {
            return new LocalGame(new LocalGameEngine());
        }
    }