Абстрактный метод с переменным списком аргументов

Я не нашел элегантный способ решить эту проблему. У меня есть абстрактный класс, который несколько других классов наследуют абстрактным методом, который может содержать от 0 до 4-5 аргументов различных типов.

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

Например, у меня есть класс Book, который наследует это и не принимает аргументов при переопределении use(), у меня есть класс Key, который наследует и принимает строку и очередь как аргументы при переопределении и т.д.

Я пробовал использовать generics, но я должен вводить используемый номер, например Item, когда он действительно зависит от класса.

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

Я попытался отправить список переменных из Объектов, но типы объектов всегда являются переменными, и я не уверен в синтаксисе для приема в наследующих классах.

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
}

Я могу просто делать что-то не так - может ли кто-нибудь предложить какую-либо помощь или понимание?

Ответ 1

Я боролся с одним и тем же вопросом, и нет идеального ответа, но я могу рассказать вам несколько вещей. Во-первых, вы в основном пытаетесь сделать что-то, что по своей сути относится к объектно-ориентированному программированию, а именно к тому, что вы пытаетесь создать переменный интерфейс. Точкой интерфейса является то, что код, который получает абстрактную версию объекта (например, Item, а не книгу), знает, как вызвать метод use(). Это означает, что они должны знать, что можно передать методу use(). Если ответ зависит от реализации абстрактного класса или интерфейса, то вам нужно убедиться, что используемый им код действительно знает, какую реализацию (книгу и т.д.) Он использует, иначе он не будет знать, как использовать использование() с соответствующими параметрами. Похоже, вам нужно реорганизовать свой код, честно говоря.

Тем не менее, есть способ ответить на ваш вопрос, как указано, без реорганизации архитектуры. Вы можете создать класс, в котором будут представлены все типы параметров, которые могут быть переданы методу use(), имеют код вызова, заданный для полей этого класса, а затем передают его методу use(). Например:

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

Затем вы можете определить метод использования в элементе следующим образом:

public abstract void use(UseParameters params);

И любой код, использующий элемент, должен будет соответствующим образом установить параметры объекта:

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);

Я просто хочу указать, что если код выше знает, что Item является книгой (каким образом он знает, как установить String и Queue, то почему бы просто не получить книгу и не пропустить абстрактный класс с использованием переменной() в целом? Но я отвлекаюсь. В любом случае, Книга затем реализует метод use() следующим образом:

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

    // Do what books do with strings and queues
}

Я думаю, что вы получите то, что хотите, но, думаю, вам стоит подумать о рефакторинге.

Ответ 2

Вы хотите шаблон объекта значения.

Определите класс, который инкапсулирует различные типы параметров в один объект значения, и пусть абстрактный метод принимает параметр этого типа. Каждая вариация параметров, которые вы рассматривали, будет иметь свой собственный класс значений.

Затем просто добавьте в класс общий тип и пусть абстрактный метод принимает параметр этого типа:

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

Чтобы использовать его, предположим, что MyItem нужен объект значения типа MyValueClass:

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

Ответ 3

Если типы, которые будут использоваться в качестве аргумента, всегда являются переменными, я не вижу причины использовать дженерики. Просто используйте простой тип объекта:

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

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

Ответ 4

Лучший подход, который я могу придумать, - группировать элементы в соответствии с поведением их метода use().

Пример

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

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

Если группируемые элементы имеют общее поведение (общее, как в той же сигнатуре метода и возвращаемом значении), вы можете определить и расширить родительский класс, который будет содержать определение этого общего поведения.

Ответ 5

Да, мы можем предоставить параметры абстрактному методу, но он должен предоставить тот же тип параметров реализованным методам, которые мы написали в производных классах.