Parameter evaluation is always associated with some context, which code manifestation is a subclass of ParamContext
class.
Every time parameter is evaluated, a context instance is created. Either it is passed in by user, or created by ParamEngine
based on user input. Context role is to generate query vector that is used to evaluate parameter output value.
Simplest implementation of context is an array of values that map one-to-one onto query vector. This is behavior of
ParamEngine.get(String parameterName, Object... values)
. This is also most intuitive way of querying parameter at first.
It has a major flaw, which is forcing user to know the exact definition of parameter - what are the levels, what is
exact order, what are their types. It is also not very flexible, as each change in parameter requires passing on new
list of query values.
Example of direct query approach:
{
name: "user.magicFactor",
inputLevels: 2,
levels: [
{name: "userLogin", type: "string"}
{name: "userAge", type: "integer"}
{name: "factor", type: "integer"}
]
}
userLogin;userAge;factor
adam;23;125
public int evaluateMagicFactor(User user) {
String login = user.login();
int age = user.age();
return paramEngine.get("user.magicFactor", login, age).getInteger();
}
More sophisticated and preferred approach is to use level creators to compose query vector
based on content of bag-like context (DefaultContext
is a default implementation of this type of context). User can
put domain objects into context and it is be level creators role to extract lower level properties out of it.
Note: context objects are not reusable, they have to be constructed per parameter evaluation (we will change this in future)
Example of level creator based approach:
{
name: "user.magicFactor",
inputLevels: 2,
levels: [
{name: "userLogin", type: "string", levelCrator: "user.login"}
{name: "userAge", type: "integer", levelCrator: "user.age"}
{name: "factor", type: "integer"}
]
}
userLogin;userAge;factor
adam;23;125
public class UserLevelCreators {
@JavaPlugin("user.login")
public String userLogin(DefaultCOntext context) {
return context.get(User.class).login();
}
@JavaPlugin("user.age")
public int userAge(DefaultCOntext context) {
return context.get(User.class).age();
}
}
public int evaluateMagicFactor(User user) {
return paramEngine.get("user.magicFactor", new DefaultCOntext(user)).getInteger();
}
Notice, that parameter definition changes - levels have an associated level creator. Now if we ever need to add another
input level to parameter (another dimension), there is no need to change code in evaluateMagicFactor
. It becomes a matter
of adding new level in parameter definition with proper level creator.
Using level creators with contexts helps avoiding code duplication and boilerplate. Level creator that was once defined
can be reused in other parameters. It encapsulate extraction of lower level properties, so user does not need to repeat
user.login()
nor user.age()
all over the code.