dimanche 5 novembre 2017

How to use Java Generics to enforce type restrictions

This question is a bit advanced so naturally also a little complicated. I will try and do my best to be as clear as possible.

As the title reads, I'd like to use Java Generics to enforce type restrictions when constructing an objects from some top level (main).

I have never really used Java generics but I found a pretty good use case for it which I am not sure how to implement.

I'd like to enforce type restriction when composing an object. Let me try to clarify with an example:

I have a top level main method here where I am evoking a NumberEngine object where I initialize and call methods of it. Notice when I call setExecuteBehavior(), I pass it an object of type RunNumberEvaluation (which along with RunStringEvaluation implements an interface called ExecutionBehavior).

As the name implies, NumberEngine works only with Numbers and not Strings, so it's inappropriate for me to pass setExecuteBehavior() an object of type RunStringEvaluation. How can I enforce this behavior at compile time?

public static void main(String[] args) {

    NumberEngine numberEngine = new NumberEngine();
    numberEngine.init("/path/to/forms");
    numberEngine.getEngineVesion();

    numberEngine.setExecuteBehavior(new RunNumberEvaluation);
    numberEngine.performExecution();


    // Here this should not compile, essentially throw me a compile error saying it can only accept 
    // an object of type RunNumberEvaluation, sincle NumberEngine can only run 
    // objects of type RunNumberEvaluation, etc...
    numberEngine.setExecuteBehavior(new RunStringEvaluation());
    numberEngine.performExecution();

}

So here I would like to basically make NumberEngine's setExecuteBehavior to only accept behavior which is relevent to it like the processing of data which pertains to numbers and not Strings. And vice-versa for StringEngine. I want StringEngine to only accept objects which pertains to Strings and not Numbers.

How can I accomplish this with Java generics?

I have included working code below as an illustration of what I'm talking about.

I have an object of type Engine which is an abstract class with many extending concrete classes such as StringEngine, NumberEngine, et cetera. I have decoupled the algorithmic functionality into an interface with classes that implement that interface.

Base Abstract Class

public abstract class Engine {
    ExecuteBehavior executeBehavior;

    public void setExecuteBehavior(ExecuteBehavior executeBehavior) {
        this.executeBehavior = executeBehavior;
    }
    public void performExecution() {
        executeBehavior.execute();
    }
    public abstract void init(String pathToResources);
}

Concrete Implementing Class 1

public class StringEngine extends Engine {
    public StringEngine() {
        executeBehavior = new RunNumberEvaluation();
    }

    @Override
    public void init(String pathToResources) {
        System.out.println("Initializing StringEngine with resources "+pathToResources);
        System.out.println("Successfully initialized StringEngine!");
    }
}

Concrete Implementing Class 2

public class NumberEngine extends Engine {
    public NumberEngine() {
        executeBehavior = new RunStringEvaluation();
    }

    @Override
    public void init(String pathToResources) {
        System.out.println("Initializing NumberEngine with resources "+pathToResources);
        System.out.println("Successfully initialized NumberEngine!");
    }
}

Algorithm Interface

public interface ExecuteBehavior {
    void execute();
}

Algorithm Implementation 1

public class RunNumberEvaluation implements ExecuteBehavior {
    @Override
    public void execute() {
        // some processing
        System.out.println("Running numeric evaluation");
    }
}

Algorithm Implementation 2

public class RunStringEvaluation implements ExecuteBehavior {
    @Override
    public void execute() {
        // some processing
        System.out.println("Running string evaluation");
    }
}

If you haven't noticed but here I'm making use of the strategy pattern where I segregate the varying algorithms into a family via interface from the static non-changing code.

Aucun commentaire:

Enregistrer un commentaire