2

Say I have this code:

IOperation<?> parameter1 = null;
ITotallyDifferentOperation<?> parameter2 = null;

switch (OPERATION_TYPE) {
  case TYPE_1:
    parameter1 = new MyOperation<Type1>();
    parameter2 = new MyTotallyDifferentOperation<Type1>();
    break;
  case TYPE_2:
    parameter1 = new MyOperation<Type2>();
    parameter2 = new MyTotallyDifferentOperation<Type2>();
    break;

...

switch (DB_OPERATION_TYPE) {
  case DB_TYPE_1:
    myMethod(parameter1, parameter2);
    break;
  case DB_TYPE_2:
    myOtherMethod(parameter1, parameter2);
    break;

...

The method which accept the two interfaces implemented by MyOperation and MyTotallyDifferentOperation:

void myMethod(final IOperation<?> operation, final ITotallyDifferentOperation<?> totallyDifferentOperation) {
  operation.processList(totallyDifferentOperation.getList());
}

totallyDifferentOperation.getList() returns a List<T> which is equal in type of the List<T> accepted by operation.processList().

This code obviously does not compile.
Are there other patterns to obtain the same result or this code can be corrected?

As requested I posted a bit more of the method. I cannot disclose any further unfortunately, and this is a mockup.
I need this kind of pattern to avoid duplication of code.

5 Answers 5

4

There is no established design pattern which will help you here.


Your problem is in your declaration of these variables:

IOperation<?> parameter1 = null;
ITotallyDifferentOperation<?> parameter2 = null;

By using the wildcard, you are effectively telling the compiler "I do not care about the type". That's not true. Eventually you do care about the type.

Fundamentally, your method is attempting to do too much and that's what's causing you problems. Break it down into multiple methods and it becomes much easier:

switch (OPERATION_TYPE) {
   case TYPE_1:
      typeOne();
   case TYPE_2:
      typeTwo();
}

private void typeOne()
{
    IOperation<Type1> parameter1 = new MyOperation<>();
    ITotallyDifferentOperation<Type1> parameter2 = new MyTotallyDifferentOperation<>();

    doTheChecks(parameter1, parameter2);
    databaseStuff(parameter1, parameter2);
}

private void typeTwo() /*identical to above, except the types*/
{
    IOperation<Type2> parameter1 = new MyOperation<>();
    ITotallyDifferentOperation<Type2> parameter2 = new MyTotallyDifferentOperation<>();

    doTheChecks(parameter1, parameter2);
    databaseStuff(parameter1, parameter2);
}

<T> void doTheChecks(IOperation<T> param1, ITotallyDifferentOperation<T> param2)
{
    ...
}

<T> void databaseStuff(IOperation<T> param1, ITotallyDifferentOperation<T> param2)
{
    switch (DB_OPERATION_TYPE) {
        case DB_TYPE_1:
            myMethod(param1, param2);
            break;
        case DB_TYPE_2:
            myOtherMethod(param1, param2);
            break;
    }
}
Sign up to request clarification or add additional context in comments.

1 Comment

You're right. The target of what I'm trying to do is eliminating the need for repeating doTheChecks() and databaseStuff() on every type.
2

Read this Java tutorial about generic method parameters. You can add a type parameter to you method to make sure that the operation and the totallyDifferentOperation are applicable for the same type:

<T> void myMethod(IOperation<T> operation,
                  ITotallyDifferentOperation<T> totallyDifferentOperation)
{
    operation.processList(totallyDifferentOperation.getList());
}

This way you move the responsibility to make sure that both parameters are typed the same way to the caller:

// compiles
myMethod(new MyOperation<Type1>(), new MyTotallyDifferentOperation<Type1>())
// does not compile
myMethod(new MyOperation<Type1>(), new MyTotallyDifferentOperation<Type2>())

This is the simplest example. You can use upper and lower bounds to make the restrictions more flexible. This might enable you to mix types as long as they are compatible (imagine an IOperation that can handle any Number input, and different ITotallyDifferentOperations that supply Long, Double, ...). This tutorial explains this.


After you added code, maybe you could have a look at this:

Operations<?> parameters = null;

switch (OPERATION_TYPE)
{
    case TYPE_1 :
        parameters = new Operations<>(new MyOperation<Type1>(), 
                                      new MyTotallyDifferentOperation<Type1>());
        break;
}
switch (DB_OPERATION_TYPE)
{
    case DB_TYPE_1 :
        myMethod(parameters);
        break;
}

static <T> void myMethod(final Operations<T> operations)
{
    operations.operation.processList(operations.totallyDifferentOperation.getList());
}

static class Operations<T>
{
    public final IOperation<T>                 operation;
    public final ITotallyDifferentOperation<T> totallyDifferentOperation;

    public Operations(IOperation<T> operation, ITotallyDifferentOperation<T> totallyDifferentOperation)
    {
        this.operation = operation;
        this.totallyDifferentOperation = totallyDifferentOperation;
    }
}

By wrapping the two operations into a typed parameter object you can make sure that they are of the same type. This means of course that your dbOperations need to accept that param object indtead of two parameters seperately.

9 Comments

This actually is the first thing I tried. But being that the initial Objects are created with a <?> wildcard, this shows a compilation error. Specifically in the method call line.
Is there a reason why you do not call the method immediately in the cases? That way you do not need to declare the wildcard variables: case TYPE_1: myMethod(new MyOperation<Type1>(), new MyTotallyDifferentOperation<Type1>())
I have other conditions to evaluate before calling the method. Unfortunately I cannot initialize the types directly on the method call.
Could you add those checks to your question?
Yes, I think in the end the idea behind both answers is similar: encapsulate both parameters in one object/method to ensure that they are both of the same type. Either way, hope it helps in the future.
|
1

I'm assuming your Operation classes and interfaces look something like this:

interface IOperation<T> {
    public List<T> processList(List<T> list);
    public List<T> getList();
}

interface ITotallyDifferentOperation<T> {
    public List<T> processList(List<T> list);
    public List<T> getList();
}

class MyOperation<T> implements IOperation<T> {
    public List<T> processList(List<T> list) {return null;}
    public List<T> getList() {return null;}
}

class MyTotallyDifferentOperation<T> implements ITotallyDifferentOperation<T> {
    public List<T> processList(List<T> list) {return null;}
    public List<T> getList() {return null;}
}

And that the error you get is something like this:

void myMethod(final IOperation<?> parameter1,
final ITotallyDifferentOperation<?> parameter2) {
    // COMPILE ERROR: processList() is not applicable for the arguments
    parameter1.processList(parameter2.getList());
}

The reason for this compile error is that myMethod() allows the type of parameter1 and parameter2 to be different from each other.

The best option (In my opinion) would be to not wildcard parameter1 and parameter2, and instead ensure that they are the same generic type, like so:

Operation<T> parameter1 = null;
ITotallyDifferentOperation<T> parameter2 = null;

Based on the IOperation interfaces any of these operations would require myMethod() to accept two parameters with the same generic types, like so:

void <T> myMethod(
final IOperation<T> operation,
final ITotallyDifferentOperation<T> totallyDifferentOperation) {
    operation.processList(totallyDifferentOperation.getList());
}

BUT

If you don't want to do any of the above, then you could change both IOperation interfaces processList methods to accept wildcards so that you can leave all the inputs as wildcards, like so:

interface IOperation<T> {
    public List<T> processList(List<?> list);
    public List<T> getList();
}

interface ITotallyDifferentOperation<T> {
    public List<T> processList(List<?> list);
    public List<T> getList();
}

The only issue then is that you wont have any compile-time type guarantee within those methods and you'd have to check them at run-time...

EDIT in response to Malte Hartwig:

Not wildcarding the parameters will not compile, as T cannot be resolved. That's the whole problem: you do not know which type it is

You don't need to know what type it is, just that they are the same type T. I have expanded on my answer here:

// remove wildcards, use a generic method, rather than deciding Type in a switch statment.
<T> void someMethod(final Class<T> klass, final int DB_OPERATION_TYPE) {
    IOperation<T> parameter1 = new MyOperation<T>();
    ITotallyDifferentOperation<T> parameter2 = new MyTotallyDifferentOperation<T>();

// ... OR ...

<T> void someMethod(final int DB_OPERATION_TYPE, IOperation<T> parameter1,
    ITotallyDifferentOperation<T> parameter2) {

    // ... Other conditions to evaluate before calling the myMethod ...

    switch(DB_OPERATION_TYPE) {
        case 1:
            myMethod(parameter1, parameter2);
            break;
        case 2:
            myOtherMethod(parameter1, parameter2);
            break;
    }
}

<T> void myMethod(final IOperation<T> operation, final ITotallyDifferentOperation<T> totallyDifferentOperation) {
    operation.processList(totallyDifferentOperation.getList());
}

<T> void myOtherMethod(final IOperation<T> operation, final ITotallyDifferentOperation<T> totallyDifferentOperation) {
    // something... ?
} 

And to call this method:

final int DB_OPERATION_TYPE = 1;
someMethod(String.class, DB_OPERATION_TYPE);
// OR
someMethod(DB_OPERATION_TYPE, new MyOperation<String>(), MyTotallyDifferentOperation<String>());

As for bringing the method call into the switch and ditching the parameters altogether: Op clarified that this is not an option.

Op clarified this in response to you only after my answer had been posted. I have now edited it out of my answer.

3 Comments

Not wildcarding the parameters will not compile, as T cannot be resolved. That's the whole problem: you do not know which type it is. As for bringing the method call into the switch and ditching the parameters altogether: Op clarified that this is not an option.
Now that you have added the method signature for someMethod I understand where T came from. Sorry that I didn't see it earlier. There is still the problem that you cannot specify the type of MyOp and MyTot.... Using Type1 or String when assigning them to the variables will still result in a compile error. I guess they have to be passed in as parameters after all.
@MalteHartwig Yea, I probably should've included someMethod from the start, I just didn't know the context Op was calling his code in. Oops, someMethod wasn't reusable, edited again. You're right including the parameters would be a good way to fix that, I also included the option of passing the type in a Class obeject. Thanks for catching that.
0

Elaborating Michael answer this is what I came up. Seems almost nice ;)

switch (operation) {
  case TYPE_1:
    processDbOperation(
      dbOperation, 
      new MyOperation<Type1>(), 
      new MyTotallyDifferentOperation<Type1>());
    break;
  case TYPE_2:
    processDbOperation(
      dbOperation, 
      new MyOperation<Type2>(),
      new MyTotallyDifferentOperation<Type2>());
    break;

...

<T> void processDbOperation(final DbOperation dbOperation, final IOperation<T> operation, final ITotallyDifferentOperation<T> totallyDifferentOperation) {
  switch (dbOperation) {
    case DB_TYPE_1:
      myMethod(parameter1, parameter2);
      break;
    case DB_TYPE_2:
      myOtherMethod(parameter1, parameter2);
      break;
}

...

Comments

0

How about additionally passing the Class object that describes your type parameter? Then you have non-erased types, basically.

2 Comments

But then you would still have to cast. You might know the type then, but still can't be sure what type the elements in the list have. If you blow this up a bit into a true Strategy pattern, it might work. It would then be similar to the previous answers, encapsulating parameter extraction and method call into one place.
You rely on the fact that the correct class is passed, sure. Another way might be to make a type-safe list that holds a class object and checks every element before adding. I am personally fine with the way Java lists are.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.