В Java, вы можете использовать переменные аргументы без использования varargs? - PullRequest
0 голосов
/ 01 февраля 2011

В Java я хочу создать несколько геометрических фигур на основе пользовательского ввода.Хитрость в том, что я не могу изменить существующий API, поэтому я не могу использовать синтаксис varargs, такой как

public Shape(Object... attrs) {}

Ввод пользователя:

shape.1.triangle.arg1 = 3
shape.1.triangle.arg2 = 4
shape.1.triangle.arg3 = 5
shape.1.triangle.arg4 = "My first triangle"
shape.2.rectangle.arg1 = 4
shape.2.rectangle.arg2 = 7
shape.2.rectangle.arg3 = "Another string label"

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

Shape s1 = new Triangle(arg1, arg2, arg3, arg4);

Или в общем случае:

String shapeType = "triangle";
Object[] args = {arg1, arg2, arg3, arg4};
// This won't work, because newInstance() doesn't take args
Shape s1 = Class.forName(shapeType).newInstance(args); 

String shapeType = "rectangle";
Object[] args = {arg1, arg2, arg3};
// This won't work, because newInstance() doesn't take args
Shape s2 = Class.forName(shapeType).newInstance(args);

Проблема в том, что конструктор для Triangle не допускает varargs (...), и я не могу его изменить.В частности, конструкторы имеют вид

public Triangle (int a, int b, int c, String label) {}

public Rectangle (int a, int b, String label) {}

Итак, как мне создать правильные фигуры на основе пользовательского ввода?

Ответы [ 4 ]

4 голосов
/ 01 февраля 2011

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

1 голос
/ 01 февраля 2011

Это интересный подход. Проблема не в том, что Triangle не хватает конструктора vararg, а в том, что newInstance() может вызывать только конструктор по умолчанию.

mschaef предложил разумный подход. Это все еще требует статического понимания форм конструктора каждой формы. Эти знания затем будут спрятаны на соответствующих фабриках. Это, наверное, лучшее решение. Проблема в том, что вы должны написать фабрику для каждой фигуры.

Однако вы можете написать код, который динамически вызывает правильный конструктор, используя API-интерфейсы отражения. Если у вас много фигур, это решит проблему в одном месте для всех из них. Вы получите объект Class, а затем вызовете getDeclaredConstructors(), чтобы получить массив конструкторов. Тип аргумента конструктора может быть запрошен с помощью getParameterTypes(). Ваш код должен был бы найти идеальный конструктор, основанный на ваших параметрах. По сути, это то, что компилятор сделал бы со статическими типами.

Это решение не особенно элегантно, но имеет свои плюсы: Если вы напишите правильно один раз, он всегда будет работать для любой новой фигуры, которую вы можете ввести. Вы можете поместить код в аккуратный служебный класс и больше никогда на него не смотреть.

В общем, я все еще рассматриваю использование отражения как нежелательный выбор.

0 голосов
/ 01 февраля 2011

Если вы хотите реализовать свое оригинальное решение (myClass.newInstance(args)) - вы можете сделать это следующим образом (одно предложение, обязательно вы можете придумать свое):
shape.1.triangle.arg1.int = 3<br> shape.1.triangle.arg2.int = 4<br> shape.1.triangle.arg3.int = 5<br> shape.1.triangle.arg4.String = "My first triangle"<br> shape.2.rectangle.arg1.int = 4<br> shape.2.rectangle.arg2.int = 7<br> shape.2.rectangle.arg3.String = "Another string label"

Ваш код может затем использовать class.getConstructor(Class<?> ... parameterType), чтобы получить правильный конструктор, и вызвать newInstance(Object ... args) для объекта Constructor. Пример:

Class<?> shape = Class.forName("triangle");  
Constructor<?> constructor = shape.getConstructor(Integer.TYPE, Integer.TYPE, Integer.TYPE, String.class);  
constructor.newInstance(Integer.TYPE, Integer.TYPE, Integer.TYPE, String.class);

Надеюсь, это было полезно.

Другая альтернатива - использовать шаблон FactoryBuilder.

shape.triangle.factory=TriangleFactory
shape.rectangle.factory=RectangleFactory
shape.1.triangle.arg1 = 3
shape.1.triangle.arg2 = 4
shape.1.triangle.arg3 = 5
shape.1.triangle.arg4 = "My first triangle"
shape.2.rectangle.arg1 = 4
shape.2.rectangle.arg2 = 7
shape.2.rectangle.arg3 = "Another string label"

А вам понадобится:

public interface IShapeFactory
{
    public IShape buildShape(Object ... args);
}

public class TriangleFactory implements IShapeFactory
{
    public IShape buildShape(Object ... args)
    {
       return new Triangle(args[0], args[1], args[2], args[3]); // You will need some casting here :)
    }
}

Все, что вам нужно сделать, это создать экземпляр фабрики, вызвать метод buildShape с аргументами, и фабрика сделает то, что нужно.

Второе решение кажется мне более приятным, и я думаю, что вам будет проще, но если формы предоставляются извне, вашим пользователям будет сложнее реализовать новые формы.

Ваш звонок.

0 голосов
/ 01 февраля 2011

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

обратите внимание, что если ваша базовая библиотека НЕ ​​МОЖЕТ делать то, что вам нужно, то нет никакого способа обойти это, например. если требуется только 4 аргумента для создания треугольника. Ваш мост может выдать какое-то неподдерживаемое исключение.

...