简体   繁体   中英

Abstract method with variable list of arguments

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?

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.

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) {
    }
}

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) { ... }
}

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.

是的,我们可以为抽象方法提供参数,但必须为我们在派生类中编写的实现方法提供相同类型的参数。

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