HiveBrain v1.2.0
Get Started
← Back to all entries
patternjavaMinor

Designing a Variable Set

Submitted by: @import:stackexchange-codereview··
0
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 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:

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.