I'm working on a small project that does mathematical calculations based on selected data in a JTable. The goal is to make the program fairly adaptable for adding new mathematical calculations in the future. For reference, some of the current calculations are: calculating the mean (average) of a set of data; calculating the percentile of each item in a set of input data; calculating the least squares linear regression (best fit linear function) for a set of data points.
My current design thought process is that to make things the math utilities flexible, there needs to be a some type of base class for analysis data that serves as input and output to some type of performAnalysis
function in a base class for all analysis classes. This lead me to a skeleton design as follows:
public enum AnalysisDataType {
Scalar,
Set,
Map
}
public interface IAnalysisData {
AnalysisDataType getType();
}
public interface IAnalaysisDataScalar extends IAnalysisData {
double getValue();
}
public interface IAnalaysisDataSet extends IAnalysisData {
int getCount();
IAnalysisData getData(int index);
}
public interface IAnalaysisDataMap extends IAnalysisData {
IAnalysisData getDomain();
IAnalysisData getCodomain();
}
public interface IDataAnalysis {
AnalysisDataType getInputDataType();
AnalysisDataType getOutputDataType();
IAnalysisData performAnalysis(IAnalysisData inputData);
}
Then, something for calculating mean
would be:
public class DataAnalysisMean implements IDataAnalysis {
AnalysisDataType getInputDataType() { return AnalysisDataType.Set; }
AnalysisDataType getInputDataType() { return AnalysisDataType.Scalar; }
IAnalysisData performAnalysis(IAnalysisData intputData) {
IAnalysisDataSet inputDataSet = (IAnalysisDataSet)inputData;
double sum = 0;
for (int i = 0; i < inputDataSet.getCount(); i++) {
IAnalysisData dataItem = inputDataSet.getData(i);
IAnalysisDataScalar dataItem = (IAnalysisDataScalar)dataItem;
sum += dataItem.getValue();
}
return new IAnalysisDataScalar() {
@Override
double getValue() {
return sum / inputDataSet.getCount();
}
@Override
AnalysisDataType getType() {
return AnalysisDataType.Scalar;
}
};
}
}
I realize a little more work needs to go into "describing" the input and output data items because no where did I mention that the input set contained scalars, but - while I already have an idea how to solve that one - I feel that the whole approach is hackish.
The thought behind this approach is that the code using interfacing with these math utilities could query the IDataAnalysis
interface to determine what the expected input and output is and can send the appropriate input as needed and parse the results appropriately. This would allow us to just need to drop-in more functionality at a later time without having to add additional controller logic to tie it in.
I feel like its wrong for a parent class - or interface in this case - to know about its possible children classes (interfaces) through the AnalysisDataType
enum and the getType
method. As always with OO programming, surely just another layer of abstraction or some type is needed, though perhaps I'm just missing out on some other design pattern that solves this exact problem.
Anyway, if anyone has advice on what I might do to better organize this, I'd much appreciate it. I'd love to hear how wrong my thoughts are and learn a better way.
I
for the interface names. I know it's a common .NET convention and not in Java but it works well for differentiating interfaces and classes. Something I wish Java had adopted. \$\endgroup\$