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?
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.
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.
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.
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.
精彩评论