4
\$\begingroup\$

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.

\$\endgroup\$
8
  • \$\begingroup\$ Are you coming from a .net background? You might want to look at the Java Code Conventions. \$\endgroup\$
    – Bobby
    Oct 11, 2011 at 14:21
  • \$\begingroup\$ Most recently, yes. My question isn't on style as it is on design. I just happen to be using Java, but I think the problem still arises if I were to do the same thing in C#, C++, or another object oriented language. \$\endgroup\$
    – Anthony
    Oct 11, 2011 at 14:59
  • \$\begingroup\$ That's why I only wrote a comment. Personally I think we should obey the local rules of a language, even if we're just passing through. But as I said, that's a mere comment. \$\endgroup\$
    – Bobby
    Oct 11, 2011 at 15:01
  • \$\begingroup\$ True, I'm planning on reading through the document, thanks for the link. \$\endgroup\$
    – Anthony
    Oct 11, 2011 at 15:02
  • \$\begingroup\$ @Bobby: Just out of curiosity, what (if any) about the code in particular would be going against Java's conventions? I'm a bit biased toward .NET but the only thing that I could identify probably would be the added 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\$ Oct 11, 2011 at 19:51

1 Answer 1

2
\$\begingroup\$

First, I get some error messages when I put this in the compiler. I had to replace the anonymous inner class that is returned by performAnalysis with a helper class. I'm not a fan of helper classes like this, so it would be nice if someone else had a better idea.

Second, I'm thinking generics would solve this quite nicely. I agree that the parent class should not need to know it's child classes, but it can obtain this data; see the methods that I have commented out. I have commented them out because I don't like them, but still want to show that it's possible. I'll admit that using .class is a bit tricky when using generics, because of type erasure. However, as long as the generic types are known at compile time, this should not be a problem.

interface IDataAnalysis<InputType extends IAnalysisData, OutputType extends IAnalysisData> {

//    public Class<?> getInputDataType( );
//    public Class<?> getOutputDataType( );

    OutputType performAnalysis(InputType inputData);
}

public class DataAnalysisMean implements IDataAnalysis<IAnalysisDataSet, IAnalysisDataScalar> {

//    
//    public Class<?> getInputDataType( ) { return IAnalysisDataSet.class; }
//    public Class<?> getOutputDataType( )  { return IAnalysisDataScalar.class; }

public IAnalysisDataScalar performAnalysis(IAnalysisDataSet inputData) {

    IAnalysisDataSet inputDataSet = (IAnalysisDataSet)inputData;
    double sum = 0;

    for (int i = 0; i < inputDataSet.getCount(); i++) {
        IAnalysisData dataItem = inputDataSet.getData(i);
        IAnalysisDataScalar dataItem2 = (IAnalysisDataScalar)dataItem;
        sum += dataItem2.getValue();
    }

    return new HelperClass( inputDataSet, sum );
}

private class HelperClass implements IAnalysisDataScalar
{
    IAnalysisDataSet inputDataSet;
    double sum = 0;

    HelperClass( IAnalysisDataSet inputSet, double sum )
    {
        this.inputDataSet = inputSet;
        this.sum = sum; 
    }

    @Override
    public double getValue() {
        return sum / inputDataSet.getCount();
    }

    @Override
    public AnalysisDataType getType() {
        return AnalysisDataType.Scalar;
    }
   }
}
\$\endgroup\$
2
  • \$\begingroup\$ That's odd, but not surprising. I don't have a Java IDE in front of me so I was writing the code from pure memory. The idea of templates came to me a little after I posted. I'll play around with the code you provided and see if I can make sense of it (more used to C++ templates and C# style generics than Java). \$\endgroup\$
    – Anthony
    Oct 12, 2011 at 13:18
  • \$\begingroup\$ There are some subtle differences between C# generics and Java generics, but it shouldn´t be a problem here. Regarding the helper class, it was needed because Java does not support closures. I realized later that there is (probably) no reason to do the division inside the class, so you could use a default implementation of IAnalysisDataScalar to simply wrap the result of the calculation. \$\endgroup\$ Oct 12, 2011 at 16:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.