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