Reputation: 135
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 was thinking about something like this...
NumberEngine<? extends Numbers> extends Engine
Not even sure if this makes sense...
I have included working code below as an illustration of what I'm attempting to communicate.
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.
Edit: I'd like to maintain the strategy pattern used here.
Upvotes: 0
Views: 784
Reputation: 1225
It's fairly simple to achieve that using generics, you were totally right trying to use generics for that
All you had to do is to change your classes like this
First the interface
public interface ExecuteBehavior<T> {
void execute();
}
Then the abstract implementation
public abstract class Engine<T> {
ExecuteBehavior<T> executeBehavior;
public void setExecuteBehavior(ExecuteBehavior<T> executeBehavior) {
this.executeBehavior = executeBehavior;
}
public void performExecution() {
executeBehavior.execute();
}
public abstract void init(String pathToResources);
}
And finally the RunNumberEngine and NumberEngine
public class RunNumberEvaluation implements ExecuteBehavior<Number> {
@Override
public void execute() {
// some processing
System.out.println("Running numeric evaluation");
}
}
NumberEngine
public class NumberEngine extends Engine<Number> {
public NumberEngine() {
executeBehavior = new RunNumberEvaluation();
}
@Override
public void init(String pathToResources) {
System.out.println("Initializing NumberEngine with resources "+pathToResources);
System.out.println("Successfully initialized NumberEngine!");
}
}
And RunStringEngine, followed by StringEngine
public class RunStringEvaluation implements ExecuteBehavior<String> {
@Override
public void execute() {
// some processing
System.out.println("Running string evaluation");
}
}
StringEngine
public class StringEngine extends Engine<String> {
public StringEngine() {
executeBehavior = new RunStringEvaluation();
}
@Override
public void init(String pathToResources) {
System.out.println("Initializing StringEngine with resources "+pathToResources);
System.out.println("Successfully initialized StringEngine!");
}
}
Upvotes: 1
Reputation: 14009
First put the "variable" classes into Engine's formal parmaeter list:
public abstract class Engine<B extends ExecuteBehavior> {
B executeBehavior;
public void setExecuteBehavior(B executeBehavior) {
this.executeBehavior = executeBehavior;
}
public void performExecution() {
executeBehavior.execute();
}
public abstract void init(String pathToResources);
}
Then you can define the subclasses the way you want:
public class StringEngine extends Engine<RunStringEvaluation> {
public StringEngine() {
executeBehavior = new RunStringEvaluation();
}
@Override
public void init(String pathToResources) {
System.out.println("Initializing StringEngine with resources "+pathToResources);
System.out.println("Successfully initialized StringEngine!");
}
}
In the example code you've provided, you don't need that. Just move setExecuteBehavior
to the subclasses and make it private.
Upvotes: 1