patternjavaMinor
Designing a Variable Set
Viewed 0 times
variabledesigningset
Problem
In my application, I need to allow the user to store variables. Variables can only be of specific types, but I effectively handle all variables the same way no matter the type. I would also like to avoid any casting. Therefore, I have several
```
public class VariableSet {
private final Map players;
private final Map numbers;
private final Set notRemovable;
private final Set usedVariableNames;
public VariableSet(){
players = new HashMap();
numbers = new HashMap();
notRemovable = new HashSet();
usedVariableNames = new HashSet();
addVariable("Current Player", new PlayerVariable(), players, false);
}
public PlayerVariable addPlayer(String variableName){
PlayerVariable var = new PlayerVariable();
addVariable(variableName, var, players, true);
return var;
}
public NumberVariable addNumber(String variableName){
NumberVariable var = new NumberVariable();
addVariable(variableName, var, numbers, true);
return var;
}
private > void addVariable(String variableName, T variable, Map map, boolean removable){
if (usedVariableNames.contains(variableName)){
throw new VariableExistsException();
}
usedVariableNames.add(variableName);
map.put(variableName, variable);
if (!removable){
notRemovable.add(variableName);
}
}
public void removeNumber(String variableName){
removeVariable(variableName, numbers);
}
public void removePlayer(String variableName){
removeVariable(variableName, players);
}
private > void removeVariable(String variableName, Map map){
if (notRemovable.contains(variableName)){
throw new VariableNotRemovableException();
}
usedVariableNames.remove(variableName);
ma
HashMaps for each variable type, which is causing lots of duplicated code. Is there a way to simplify my class?```
public class VariableSet {
private final Map players;
private final Map numbers;
private final Set notRemovable;
private final Set usedVariableNames;
public VariableSet(){
players = new HashMap();
numbers = new HashMap();
notRemovable = new HashSet();
usedVariableNames = new HashSet();
addVariable("Current Player", new PlayerVariable(), players, false);
}
public PlayerVariable addPlayer(String variableName){
PlayerVariable var = new PlayerVariable();
addVariable(variableName, var, players, true);
return var;
}
public NumberVariable addNumber(String variableName){
NumberVariable var = new NumberVariable();
addVariable(variableName, var, numbers, true);
return var;
}
private > void addVariable(String variableName, T variable, Map map, boolean removable){
if (usedVariableNames.contains(variableName)){
throw new VariableExistsException();
}
usedVariableNames.add(variableName);
map.put(variableName, variable);
if (!removable){
notRemovable.add(variableName);
}
}
public void removeNumber(String variableName){
removeVariable(variableName, numbers);
}
public void removePlayer(String variableName){
removeVariable(variableName, players);
}
private > void removeVariable(String variableName, Map map){
if (notRemovable.contains(variableName)){
throw new VariableNotRemovableException();
}
usedVariableNames.remove(variableName);
ma
Solution
The only way to do what you are trying to do, and remove the duplication, is really to cast.
What I would do is to use this kind of map:
To add something to the map, you could do:
And to get:
You could even write a generic method:
Note that this shouldn't even give any compiler warnings.
Leaking inner variable
Considering your
Whoops! I broke it!
The simple fix is this:
That is, return a copy of the data, then I can manipulate it all I want - I won't break anything!
I wouldn't use a
What I would do is to use this kind of map:
private final Map, Map> variables;To add something to the map, you could do:
variables.get(NumberVariable.class).put("key", value);And to get:
return (NumberVariable) variables.get(NumberVariable.class).get("key");You could even write a generic method:
private T getVariable(Class clazz, String key) {
return clazz.cast(variables.get(clazz).get(key));
}Note that this shouldn't even give any compiler warnings.
Leaking inner variable
Considering your
getVariableNames method, imagine if I would call your code like this:myVariableSet.getVariableNames.clear();Whoops! I broke it!
The simple fix is this:
public Set getVariableNames() {
return new HashSet<>(usedVariableNames);
}That is, return a copy of the data, then I can manipulate it all I want - I won't break anything!
I wouldn't use a
usedVariableNames at all though and just do a map.containsKey(key) directly. If you want to make sure that all variable names are unique though, even if they are of different classes, then go ahead and keep this variable.Code Snippets
private final Map<Class<? extends Variable>, Map<String, Variable>> variables;variables.get(NumberVariable.class).put("key", value);return (NumberVariable) variables.get(NumberVariable.class).get("key");private <T> T getVariable(Class<T> clazz, String key) {
return clazz.cast(variables.get(clazz).get(key));
}myVariableSet.getVariableNames.clear();Context
StackExchange Code Review Q#81829, answer score: 5
Revisions (0)
No revisions yet.