简体   繁体   中英

Subclass-specific override in Java

My problem is as follows:

We have an Algorithm that works internally with

  • Expression-objects that have a "String getContent()" method
  • Manipulator-objects that manipulate on Expressions using the "Expression manipulate(Expression e)" method

This will become a framework in Java. To solve a real problem, one needs to give a specific implementation of both an Expression and a Manipulator and the Algorithm class will do the rest.

Let's say we need a ProblemExpression and a ProblemManipulator for a specific problem.

The ProblemExpression may contain a lot of new fields, which can be used by the ProblemManipulator.

Right now, I can only think of two ways to write clean code:

  • Let ProblemManipulator.manipulate assume its arguments are ProblemExpressions
  • Use instanceOf

But I've got the feeling this is not how I should do it. Any other suggestions?

Regards and thank you in advance,

Xaero.

Sounds like you should use a Generic. Like

interface Manipulator<E extends Expression> {
    public void manipulate(E expression);
}

class ProblemManipulator implements Manipulator<ProblemExpression> {
    public void manipulate(ProblemExpression expression) {
        // expression is a ProblemExpression
    }
}

As "Problem" is a different problem, it can be an interface that extends Expression like so:

interface IProblemExpr extends Expression
{   //additional methods
}

class ProblemExpression implements IProbExpr
{
}

class ProblemManipulator()
{
    ProblemManipulator(IProblemExpr expr)
    {
     ..
    }
}

Generics are not enough, if both ProblemExpresions and ProblemManipulators can be accessed publicly. At first i thought some kind of factory framework would do the trick. Ie, either Expressions need to be able to create Manipulators or vice-versa.

for example, say ProblemManipulators were private inner classes of ProblemExpressions - obtained from Expression#createManipulator(...).

However, this does not quite do the trick... in the end, if the Algorithm is allowed to 'hold onto references to' both the Expression and Manipulator, and can obtain different unrelated implementations, then the Algorithm implementation can always (if incorrectly written) wind up invoking the wrong Manipulator for a given Expression - nothing can be done at compile time to prevent this runtime mistake as all Manipulators can be invoked with any Expression.

So, it seems to me that invocation of the Manipulator (or Expression) must 'go thru' the Expression (or conversely the Manipulator) thus ensuring that the correct Manipulator is invoked for the given Expression.

Ie, Expression needs 'manipulate()' method which delegates to the appropriate Manipulator.

I studied the way generics work, and I came up with the following solution:

First, I created a two classes, one for the expression and one for the manipulator:

public class ObjectExpression { }

public class ObjectManipulator <E extends ObjectExpression> {

    public void calculate(Set<E> objects) {
        ... // Do something
    }
}

Next, I created an Algorithm class, which is generic. Two classes are needed:

  1. Some expression

  2. Something that manipulates this type of object

We get:

public class Algorithm <F extends ObjectExpression, E extends ObjectManipulator<F>> {
    E om;

    public Algorithm( E om ) {
        this.om = om;
    }

    public void run(Set<F> objects) {
        om.calculate(objects);  
    }   
}

Then, I created an implementation for the String case: we need an expression and a manipulator

public class StringExpression extends ObjectExpression {
}

public class StringManipulator extends ObjectManipulator<StringExpression> {

    @Override
    public void calculate(Set<StringExpression> objects) {
        // Do String stuff
    }
}

Then, we can run the Algorithm as follows for Objects:

Algorithm<ObjectExpression, ObjectManipulator<ObjectExpression>> algo1 = new Algorithm<ObjectExpression, ObjectManipulator<ObjectExpression>>(manipo);
Set<ObjectExpression> objects = new HashSet<ObjectExpression>();
... // fill set
algo1.run(objects);

And for Strings:

StringManipulator manips = new StringManipulator();
Algorithm<StringExpression, StringManipulator> algo2 = new Algorithm<StringExpression, StringManipulator>(manips);
Set<StringExpression> strings = new HashSet<StringExpression>();
... // fill set
algo2.run(strings);

To me, this seems an elegant solution. What do you think? Any alternatives/improvements?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM