Jjang
Jjang

Reputation: 11444

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!

Upvotes: 56

Views: 39213

Answers (7)

Manius
Manius

Reputation: 3644

Hard to know if this completely addresses the OP's problem (only 9 years late!) but given the provided info this would seem to do so. Design Patterns page 114 has an example where a factory has multiple create() methods, which could translate to something like this.

public interface ShapeFactory {
  Circle createCircle(int radius);
  Triangle createTriangle(int l1, int l2, int l3);
}

public class MockShapeFactory {
  Circle createCircle(int radius) {
    return new MockCircle();
  }

  Triangle createTriangle(int l1, int l2, int l3) {
    return new MockTriangle();
  }
}

public class RasterShapeFactory {
  Circle createCircle(int radius) {
    return new MockCircle(radius);
  }

  Triangle createTriangle(int l1, int l2, int l3) {
    return new MockTriangle(l1, l2, l3);
  }
}

Each created Shape can still implement a common interface despite taking different parameters. (Ignoring the 'questionableness' of having these classes implement a draw() method in this theoretical example...)

public interface Shape {
  draw();
}

Upvotes: 0

Adrian
Adrian

Reputation: 3711

The factory parameters are not mandatory to be the object-to-construct constructor’s parameters but merely information necessary for the construction logic; e.g. a DataSource factory method could receive no parameters, all of them might already be present (aka configured) into the factory (e.g. maxConnections). So you’re factory method’s parameters could be anything, e.g. an object containing the object-to-construct constructor’s parameters; additionally, that parameters-object could be used to identify the kind of object to create.

According to the book (Design Patterns):

Another variation on the pattern lets the factory method create multiple kinds of products. The factory method takes a parameter that identifies the kind of object to create. All objects the factory method creates will share the Product interface. In the Document example, Application might support different kinds of Documents. You pass CreateDocument an extra parameter to specify the kind of document to create.

Upvotes: 0

user5650029
user5650029

Reputation: 35

You can use a class to wrap the factory arguments, like this:

public interface ShapeArguments{
}

public class CircleArguments implements ShapeArguments{
 ...

 public CircleArguments(... radius,... x,... y){
    ...
 }
}

private interface ShapeFactory{
        public Shape create(ShapeArguments args);
    }

    private class CircleFactory implements ShapeFactory{
        public Shape create(ShapeArguments args){
            CircleArguments circleArgs = (CircleArguments)args;
            return new Circle(circleArgs.radius, circleArgs.x,circleArgs.y);
        }
    }

If there are any common arguments between the shape arguments you can use the inheritence to manage it better

Upvotes: 1

inf3rno
inf3rno

Reputation: 26129

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

Upvotes: 0

Peter Lawrey
Peter Lawrey

Reputation: 533492

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.

Upvotes: 7

Random42
Random42

Reputation: 9159

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

Upvotes: 37

djechlin
djechlin

Reputation: 60758

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.

Upvotes: 9

Related Questions