Factory in Java when concrete objects take different constructor parameters
I'm trying to implement a Factory pattern in Java. I have a class called Shape which Circle and Triangle extends. The problem is that Shape constructor gets only 2 parameters while Circle gets 3 parameters and so is Triangle (which I won't show in the code section because is identical to Circle). To demonstrate it better:
private interface ShapeFactory{
public Shape create(int x, int y);
}
private class CircleFactory implements ShapeFactory{
public Shape create(float radius, int x, int y){ //error
return new Circle(radius, x,y);
}
}
Any ideas how to overcome this problem? I must not recieve an input from user inside the factory (must be recieved from outside).
Thanks!
You have two options:
1) Abstract Factory:
RectangularShape extends Shape
RoundShape extends Shape
and RectangularShapeFactory
and RoundShapeFactory
2) Builder (see also Item 2 in Effective Java)
public Shape {
private final int x;
private final int y;
private final double radius;
private Shape(Builder builder) {
x = builder.x;
y = builder.y;
radius = builder.radius;
}
public static class Builder {
private final int x;
private final int y;
private double radius;
public Builder(int x, int y) {
this.x = x;
this.y = y;
}
public Builder radius(double radius) {
this.radius = radius;
return this;
}
public Shape build() {
return new Shape(this);
}
}
}
//in client code
Shape rectangle = new Shape.Builder(x,y).build();
Shape circle = new Shape.Builder(x,y).radius(radiusValue).build();
What you are trying to do is simply impossible. If the constructor arguments are different, then the client code will have to do different work for a Circle
as for a Square
and you can't solve this with uniform code. If there is other work the factory is doing besides handling the constructor arguments that you believe should happen in a factory, then you need to post this to your question and state the difficulty you are having in factoring out this common code-work.
All of your implementations must take the same number of arguments, you have three options:
- have the factory store the addition arguments so you only need to provide the centre for example.
- have the factory take all arguments even though some factories might ignore some of them.
- have an argument be variable length. e.g. 'double...' the problem with this is the caller needs to know what the factory needs which defeats the purpose of a factory. IMHO.
Having a Shape interface is usually a bad design, because it is very limited. You need different information to describe different shapes. Resize is a good example for this. For a circle you need to change the radius, for the rectangle you need to change both sides, which means passing two parameters instead of one.
You can overcome this by passing some sort of shape descriptor, for example a rectangle the actual shape must fit in. So it can be resized properly assuming that all your shapes are predefined with classes and all you want to scale them. If you want to have custom shapes then the shape descriptor must be extended somehow to contain all the information necessary for the custom shapes, but stay compatible with the existing shapes. That is not necessarily very hard, you can add a property or a parameter that can be null. Here I add only new parameters.
private interface ShapeFactory{
public Shape create(float x, float y, float width, float height);
}
private class CircleFactory implements ShapeFactory{
public Shape create(float x, float y, float width, float height){
float radius = Math.min(width, height);
return new Circle(radius, x, y);
}
}
Another thought that you use the factory usually this way (ofc. the upper can be good too depending on what you want):
private interface ShapeFactory{
public Shape create(float x, float y, float width, float height, bool isCircle);
}
private class MyShapeFactory implements ShapeFactory{
public Shape create(float x, float y, float width, float height, bool isCircle){
if (isCircle)
return new Circle(Math.min(width, height), x, y);
else
return new Rectangle(width, height, x, y);
}
}
So the factory does not necessarily has the same parameters as the constructors. Many people have this impression, because I guess they try to automate factories and pass only a class list without any info about how to instantiate them. It is the same mistake people use to commit by automated DI containers as well.
What is really important here whether the upper level code wants to know about what kind of Shape implementation it gets back. But in some cases it can happen, that you have or refactor to some sort of general descriptor. For example you don't necessarily have a Shape.scale(width, height)
method, and if so, you won't be able to resize your circle or rectangle, because just as by constructors, the scaling is different there. But if all you want is call something like Shape.draw(canvas)
, then I guess you are good to go.
I found a similar question with a similar answer meanwhile, maybe you can learn from that too: https://softwareengineering.stackexchange.com/a/389507/65755