Craig Innes Craig Innes - 1 month ago 5x
JSON Question

Choosing a strategy via JSON config

I am implementing a learning agent in Java/Kotlin. Part of this agent's functionality involves it searching through a large list of possible options. There are multiple good ways to search the possibility space, and I frequently change my mind about which one is the best. Therefore, I decided to implement it as a strategy pattern.

class Agent(val searchStrategy : SearchStrategy){
fun search(input : InputGraph) : Result{

interface SearchStrategy{
fun search(input : InputGraph) : Result

class FastSearch : SearchStrategy{
//implementation here

class AccurateSearch : SearchStrategy{
// implementation here

class ExperimentalSerach : SearchStrategy{
// implentation here

Recently, I've decided to run a large suite of experiments which test the effectiveness of various system parameters. This is done through a python script, which kicks of each experiment by running the compiled jar with a different config.json file as an argument. Something like:

"numSamples" : 5000,
"environmentDifficulty" : 3,
"hazardProbability" : 0.4,

I now want to give the experimenter the ability to configure the strategy used by the agent as well. What is the best way to do this? My immediate thought is that I could just add an additional string field to the config.json:

"numSamples" : 5000,
"environmentDifficulty" : 3,
"hazardProbability" : 0.4,
"agentStrategy": "FastSearch"

Then construct it in the main system with an when or if branch:

val searchStrategy = when(config.agentStrategy){
"FastSearch" -> FastSearch()
"AccurateSearch" -> AccurateSearch()
"ExperimentalSearch" -> ExperimentalSearch()
val agent = agent(searchStrategy)

But this seems like it will start to get awkward / difficult to maintain if I begin to add more strategies. Is there a better way?


One way to solve it is to use class name to load and create an instance of the strategy like so:

val agentClass = classLoader.loadClass(config.agentStrategy)!!
val agent = agentClass.newInstance() as SearchStrategy

Another way would be to register all search strategies and then match one using given config like so:

class SearchStrategies(val strategies: List<SearchStrategy>){
    fun findForConfig(config:Config) = strategies.find { }

//somewhere at boot time
val searchStrategies = SearchStrategies(listOf(FastSearch(), AccurateSearch()))

//when needed
val config = ...
val agent = searchStrategies.findForConfig(config)

Last but it's also possible to make use of SPI to achieve greater extensibility and standard complianace at the cost of complexity.