Abstract method with variable list of arguments

45,559

Solution 1

I've struggled with the same question, and there's not a perfect answer, but I can give you a few things to consider. First, you're basically trying to do something that is inherently against Object Oriented Programming, which is that you're trying to create a variable interface. The point of an interface is that code that gets an abstract version of the object (the Item rather than the Book, for example), knows how to invoke the use() method. This means that they must know what can be passed to the use() method. If the answer depends on the implementation of the abstract class or interface, then you need to ensure that the code using it actually knows what kind of implementation (Book, etc.) that it's using, otherwise it's not going to know how to invoke use() with the appropriate parameters anyway. It sounds like you need to refactor your code, in all honesty.

However, there is a way to answer your question as stated without refactoring the architecture. You could create a class that's data is all of the different types of parameters that could possibly be passed to the use() method, have the calling code set the fields of that class, and then pass that to the use() method. For example:

public class UseParameters {
    private String string;
    private Queue queue;
    // Any other potential parameters to use(...)

    public void setString(String string) {
        this.string = string;
    }

    public String getString() {
        return string;
    }

    // All of the other accessor methods, etc.
}

Then, you could define the use method in Item like this:

public abstract void use(UseParameters params);

And any code using an Item would have to set the parameters of the object appropriately:

Item item = // However you're going to get the item
UseParameters params = new UseParameters();
params.setString("good string");
params.setQueue(new Queue());
item.use(params);

I just want to point out that if the code above knows the Item is a Book (which is how it knows to set the String and Queue, then why not just get a Book and skip needing an abstract class with a variable use() method altogether? But I digress. Anyway, the Book would then implement the use() method like so:

@Override
public void use(UseParameters params) {
    if(params.getString == null || params.getQueue() == null)
        // throw exception

    // Do what books do with strings and queues
}

I think that gets you what you want, but you should consider refactoring, I think.

Solution 2

What you want is the Value Object Pattern.

Define a class that encapsulates the various parameter types into one value object, and have the abstract method accept a parameter of this type. Each variation of parameters you were considering would have its own value class.

Then simply add a generic type to the class and have the abstract method accept a parameter of that type:

public abstract class Item<V> {
    public abstract void use(V v);
}

To use it, suppose MyItem needs a value object of type MyValueClass:

public class MyItem extends Item<MyValueClass> {
    public void use(MyValueClass v) {
    }
}

Solution 3

If the types to be used as argument are always variable I don't see a reason to use generics. Just use plain Object type:

public abstract class Item {
    public abstract void use(Object ... arguments);
}

public class Book extends Item {
    public void use(Object ... arguments) { ... }
}

Solution 4

The best approach I can think of is to group the items according to the behavior of their use() method.

Example

public abstract class QueueableItem {
    public abstract void use(String, Queue);
}

public abstract class OrdinaryItem{
    public abstract void use(String);
}

If the grouped items share a common behavior (common as in same method signature & return value), you can define and extend a parent class that will contain the definition of this common behavior.

Share:
45,559

Related videos on Youtube

kaledev
Author by

kaledev

Updated on January 22, 2020

Comments

  • kaledev
    kaledev over 4 years

    I haven't quite found an elegant way to solve this issue. I have an abstract class that several other classes are inheriting with an abstract method that can contain anywhere from zero to 4-5 arguments of varying types.

    public abstract class Item {
    public abstract void use();
    }
    

    For instance, I have a Book class that inherits this and takes no arguments when overriding use(), I have a Key class that inherits and takes a String and a Queue as arguments when overriding, etc...

    I've tried using generics but I have to input the number used, such as Item, when it actually depends on the class.

    public abstract class Item<T,U> {
    public abstract void use(T arg1, U arg2); //Number of arguments/types could be more or less
    }
    

    I've tried sending a variable list of Objects but the object types are always variable and I've unsure as to the syntax to receive in the inheriting classes.

    public abstract class Item<T> {
    public abstract void use(T... arguments);
    }
    
    public class Book extends Item<?> {
    public void use(?);
    }
    
    public class Book extends Item<String, Queue> { //Wrong number of arguments since I can't use Item<T...>
    public void use(String str, Queue q); //fails
    }
    

    I may just be doing something wrong - can anyone offer any assistance or insight?

    • Montre
      Montre almost 11 years
      If you do Item it = new Book();, how do you propose calling the method Book.use(String,Queue); on it if the compiler can't know what the correct argument types are? And if you don't expect to ever have a subclass of Item in a variable of the parent class, why does the method need to be abstract?
  • kaledev
    kaledev almost 11 years
    Thank you for the detailed answer. I'm still at a point where I can redesign the approach - which appears to be the best option. There may be enough similarity to group into several interfaces as mentioned in a below post. If not it may just make sense to abandon the interface and make sure the use is included myself.
  • Javi Marzán
    Javi Marzán over 4 years
    7 years later, I got to the same solution xD. I think this should be the accepted answer, as it uses generic types which I find more clean than the original solution. In my case, I needed this for the queries/commands pattern (CQRS), so I need an interface as a guide for the commands/queries I create. Didn't get to anything better than this solution. It's a bit undermining to have to create a new class everytime I want to use more than one parameter in a qry/cmd (if there's only one, you just pass that type as the generic).