0

In Java, I want to create a few geometric shapes based on user input. The trick is that I can't change the existing API, so I can't use varargs syntax like

public Shape(Object... attrs) {}

User input:

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"

Should lead to method invocations like:

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

Or generically to:

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

The problem is that the constructor for Triangle does not allow varargs (...), and I can't change it. Specifically, the constructors are

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

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

So how do I create the right shapes based on user input?

2
  • not understanding the problem. Your code should work if the classes rectangle and triangle have appropriate constructors. Commented Feb 1, 2011 at 20:01
  • What is the signature of the constructor you are restricted to using? Commented Feb 1, 2011 at 20:01

4 Answers 4

4

Wrap the triangle and square classes with a factory object. The factory can then determine what to create based on what you pass in, and the signatures of the underlying triangle and rectangle objects don't have to change.

Sign up to request clarification or add additional context in comments.

3 Comments

Then the factory has to know the signatures of all possible shapes. Is that the best I can do?
Each factory would need to know all the constructor forms for the particular shapes it builds. I have described an alternative, but people will either love it or hate it.
It's worse than that... the factory has to know the signatures of all the possible shapes, but it also has to know how to pick the specific kind of shape to create. One way to fix this would be to have the general 'ShapeFactory' class maintain a list of child Factories for specific types of Shapes. Then, the ShapeFactory could give each of these child factories a shot at either creating a Shape or passing to the next child. Make the list extensible, and you've pulled most of the policy decisions out of the ShapeFactory.
1

This is an interesting approach. The problem is not that Triangle lacks a vararg constructor, but rather the fact than newInstance() can only invoke the default constructor.

mschaef has proposed a reasonable approach. It does still require a static awareness of the constructor forms of each shape. This knowledge will then be hidden in the respective factories. It is probably the best solution. The problem is that you have to write a factory for each shape.

However, you can write code that dynamically invokes the correct constructor using the reflection APIs. If you have lots of shapes, that will solve the problem in one place for all of them. You would obtain the Class object, and then call getDeclaredConstructors() to obtain an array of constructors. Constructor argument types can be queried by getParameterTypes(). Your code would have to find the ideal constructor based on the parameters you have. This is essentially what the compiler would have done with static types.

This solution is not particularly elegant, but it does have plus points: If you write it correctly once, it will always work for any new shape that you might introduce. You can place the code in a neat utility class and never look at it again.

In general, I still consider using reflection as an undesirable choice.

2 Comments

Funny, I think the reflection solution is very elegant. :) It's even a little simpler, because I can get the arg types from the user, and use getConstructor() with a specific type list, and bomb if it's unavailable.
This is basically bringing a bit of 'dynamic language' into Java. If you really do have the constraint where you don't know what your constructor signatures will be until runtime, it's about the best you can do.
0

I think you need to use the Bridge Pattern, or at least a variant of it. Your bridge can follow the signature you would like to have, and make decisions on how to invoke the underlying class.

note that if your underlying library CANNOT do what you need it to do, there is no way around that e.g. if it only takes 4 arguments for making a Triangle. Your bridge could throw some sort of not supported exception.

Comments

0

If you want to implement your original solution (myClass.newInstance(args)) - you can do it this way (one suggestion, sure you can come up with your own):
shape.1.triangle.arg1.int = 3
shape.1.triangle.arg2.int = 4
shape.1.triangle.arg3.int = 5
shape.1.triangle.arg4.String = "My first triangle"
shape.2.rectangle.arg1.int = 4
shape.2.rectangle.arg2.int = 7
shape.2.rectangle.arg3.String = "Another string label"

Your code can then use the class.getConstructor(Class<?> ... parameterType) to get the correct constructor, and invoke the newInstance(Object ... args) on the Constructor object. Example:

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

Hope this was helpful.

Another alternative is to use a FactoryBuilder pattern.

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"

And you will need:

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

All your code will need to do is instantiate the factory, invoke the buildShape method with the arguments and the factory will do what it needs to do.

The second solution seems nicer to me, and i think easier to you, but if shapes are provided from the outside it might be more difficult on your users to implement new shapes.

Your call.

Comments

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.