samedi 13 août 2016

state method graph traversal in Java design

I am currently making an test automation application that has a bunch of states and each states will have its own methods that can be ran only in that state.

For example, for a game automation, there would be following 7 states:

UpdateInfoScreen, LoginScreen, ServerSelectScreen, ChannelSelectScreen, CharacterSelectScreen, CharacterCreation, InGameScreen.

These 7 states will extend an abstract class called GameScreen (which holds a driver object to be used to send actions to the application).

These states are designed as following:

public class UpdateInfoScreen extends GameScreen {

    public UpdateInfoScreen(GameContext context) {
        super(context);
    }

    @Override
    protected GameState myGameState() {
        return GameState.UPDATEINFO;
    }

    public void goToLogin() {
        System.out.println("goToLogin");
        context.setState(GameState.LOGIN);
    }
}

The GameState is an enum as following:

public enum GameState {
UPDATEINFO,
LOGIN,
SERVERSELECT,
CHANNELSELECT,
CHARACTERSELECT,
CHARACTERCREATION,
INGAME;
}

GameContext is a class that will keep info about the current state. all GameScreen objects will hold this object and update it. I following:

public class GameContext {
    public GameState state;
    public GameContext() {
    }

    public GameState getState() {
        return state;
    }

    public void setState(GameState state) {
        this.state = state;
    }
}

Here is an example of the main()

public static void main(String[] args) {
    GameContext context = new GameContext();
    // initial state is set as UPDATEINFO.
    context.setState(GameState.UPDATEINFO);

    UpdateInfoScreen updateinfo = new UpdateInfoScreen(context);
    LoginScreen login = new LoginScreen(context);
    ServerSelectScreen serverselect = new ServerSelectScreen(context);
    ChannelSelectScreen channelSelect = new ChannelSelectScreen(context);
    CharacterSelectScreen characterSelect = new CharacterSelectScreen(context);
    CharacterCreationScreen characterCreation = new CharacterCreationScreen(context);
    InGameScreen ingame = new InGameScreen(context);

    // GameState.LOGIN -> GameState.CHARACTERCREATION method 1
    updateinfo.goToLogin(); // state transition method
    login.goToServerSelect(); // state transition method
    serverSelect.channelSelect(); // state transition method
    channelSelect.goToCharacterCreation(); // state transition
    characterCreation.createCharacter(); // NON-state transition method.

    // GameState.LOGIN -> GameState.CHARACTERCREATION method 2
    updateinfo.goToLogin(); // state transition method
    login.gotoCharacterCreation(); // state transition. same destination as above, but going through 2 less states.
    characterCreation.createCharacter(); // NON-state transition method

    // What I want to do.
    stateHelper.goTo(GameState.CHARACTERCREATION, context); // automatically find the most optimal path. In this case, Method 2!
    characterCreation.createCharacter(); // NON-state transition method
}

As you can see, previously I have to call methods from each State classes after every state transitions. But there can be multiple ways of getting from a state to another, and one way could be more efficient than the other. I would like to find this path programmatically without the user having to worry about which states to go to.

I can diagnose that the GameScreen classes (such as UpdateInfoScreen()) are vertexes and their state transition methods (such as goToLogin()) are edges (NON-state transition methods such as createCharacter() are just methods.), meaning this problem can be solved by using a directed graph structure.

I thought of couple ways of redesigning this to integrate this graph structure, and they all involve reflection.

One example would be by adding a Map to all GameScreen classes like so:

abstract class GameScreen {
    private GameState gameState;
    protected GameContext context;
    Map<GameState, String> availableStateTransitions; // << ADDITION

    public GameScreen(GameContext context) {
        this.gameState = myGameState();
        this.context = context;
        setAvailableStateTransitions(); // << ADDITION
    }

    protected abstract GameState myGameState();
    protected abstract void setAvailableStateTransitions(); // << ADDITION
}

And an example GameScreen class would look like:

public class UpdateInfoScreen extends GameScreen {
    public UpdateInfoScreen(GameContext context) {
        super(context);
    }
    @Override
    protected GameState myGameState() {
        return GameState.UPDATEINFO;
    }

    @Override
    protected void setAvailableStateTransitions() { // << ADDITION
        this.availableStateTransitions.put(GameState.LOGIN, "goToLogin");
    }

    public void goToLogin() {
        System.out.println("goToLogin");
        context.setState(GameState.LOGIN);
    }
}

The map is a list of all states that you can go to from that certain state, mapped to the String of the method name that takes you to the other state.

Then maybe I could have like a GraphStateHelper class that would manage all these state transitions... (Can't think of this)

But this is as far as I can get with this design. I know I can invoke these String methods using reflection, but I want to get away from reflection and I believe there can be a much simpler design than this.

I don't even know if you guys will even understand what I am trying to achieve, but if someone is, I would appreciate some help with designing this!

Thanks guys if you have read all this!





Aucun commentaire:

Enregistrer un commentaire