Reputation: 6154
I am trying to use a config file to allow a user to choose a concrete clustering algorithm that implements some abstract class or interface. Each algorithm may have slightly different pre-requisite inputs. A small conceptual example:
KMedoids
just needs to have k
set by the user.SpectralClustering
also needs k
but also needs several other things like Enum
s representing the type of graph, and the type of laplacian to use in its internal calculations.The things the two have in common are defined in an abstract class.
To make things more general, I made up a dummy example. Let's say I have a Configuration
class that will be passed to an "engine" or "driver" of sorts to complete a job based on the instance of Configuration
generated.
public class Configuration {
private AbstractAlgorithm algo;
private AbstractParameter param1;
private ConcreteEnum.Enum1 param2;
private ConcreteEnum.Enum2 param3;
// getters and setters...
Then I have the classes
abstract class AbstractAlgorithm{
AbstractParameter abstractParam;
public void setParam(AbstractParam p) {
this.abstractParam = p;
}
public RandomObject runAlgo(DataObject data);
}
and ConcreteAlgorithm
:
public class ConcreteAlgorithm extends AbstractAlgorithm {
ConcreteEnum.enum1 concreteParam1;
ConcreteEnum.enum2 concreteParam2;
public ConcreteAlgorithm(ConcreteParameter p1, ConcreteParameter p2) {
this.concreteParam1 = p1;
this.concreteParam2 = p2;
}
public abstract void runAlgo(DataObject data) {
if (abstractParam == null) {
throw new IllegalStateException();
} else {
// do calculations based on enums and abstractParam value
}
}
My question then is: how would the accompanying .yaml
file look so that it could be parsed into an instantiated Configuration
object that could be passed on to something that uses it?
Upvotes: 0
Views: 1635
Reputation: 39708
As usual with serialization, your classes should have a constructor with no arguments:
abstract class AbstractAlgorithm {
public AbstractParameter abstractParam;
}
public class ConcreteAlgorithm extends AbstractAlgorithm {
public ConcreteEnum.Enum1 param1;
public ConcreteEnum.Enum2 param2;
}
Whatever interface they have besides the fields is irrelevant to this question. I made the fields public since there is not really a point for them not to be when you're doing deserialization unless you want to process the given values in setters, in which case you would use setters.
Now with these types defined, loading will work as follows:
Constructor constructor = new Constructor();
constructor.addTypeDescription(
new TypeDescription(ConcreteAlgorithm.class, new Tag("!concrete")));
yaml = new Yaml(constructor);
AbstractAlgorithm = yaml.loadAs(someInput, AbstractAlgorithm.class);
and the YAML being loaded would look like this:
--- !concrete
abstractParam: spam
param1: egg
param2: sausage
The ---
is the directives end marker, which starts the document's content and is optional. We use it here to make clear that the following tag applies to the root node of the document.
!concrete
is that tag for the root node. Since we register it with SnakeYAML, it will lead to SnakeYAML constructing a ConcreteAlgorithm object from this file.
The following key-value pairs give values to each of the fields of a ConcreteAlgorithm, including the inherited field. SnakeYAML will process the field values depending on the nature of the used classes – AbstractParameter
may want to have a nested mapping in the YAML file if it's a class. If AbstractParameter
is another abstract class and has ConcreteParameter
as implementation, you would do
--- !concrete
abstractParam: !cp
droggel: jug
param1: egg
param2: sausage
and register !cp
as tag for ConcreteParameter
. The nested mapping containing the key droggel
will then generate an instance of ConcreteParameter
and be assigned to the field abstractParam
.
Since you can give tags on any level, you can nest fields with abstract class type anywhere in the structure you load your YAML into.
Upvotes: 2