vegeta
vegeta

Reputation: 297

How to initialize the classes in factory method which have constructors with multiple parameters

Lets say I have a Shape interface which calculates the area of shape. I add 2 implementations Rectangle and Square. The challenge I see is both implementations have their own multiple argument constructors. How do I initialize them using a factory pattern. I wanted to solve it using java.

public class Rectangle implements Shape {
int length;
int breadth;

public Rectangle(List<String> parameters) {
    this.length = Integer.parseInt(parameters.get(0));
    this.breadth = Integer.parseInt(parameters.get(1));
}

@Override
public int area() {
    return length * breadth;
}

}

public class Square implements Shape {
int edge;

public Square(List<String> parameters) {
    this.edge = Integer.parseInt(parameters.get(0));
}

@Override
public int area() {

    return edge * edge;
}

}

public interface Shape {
int area();

}

public interface ShapeFactory {
public Shape make(String shapeType);
public List<String> getParameters(String shapeType);

}

public class ShapeFactoryImpl implements ShapeFactory {
Map<String, List<String>> shapeInitMap = new HashMap<>();

public void init(){
    shapeInitMap.put("Circle", Arrays.asList(new String[]{"4"}));
    shapeInitMap.put("Rectangle", Arrays.asList(new String[]{"2","3"}));
}

@Override
public Shape make(String shapeType) {
    switch (shapeType) {
    case "Circle":
        return new Square(getParameters(shapeType));
    case "Square":
        return new Rectangle(getParameters(shapeType));
    default:
        break;
    }
    return null;
}

@Override
public List<String> getParameters(String shapeType) {

    return shapeInitMap.get(shapeType);
}

}

Upvotes: 1

Views: 1680

Answers (1)

jferard
jferard

Reputation: 8180

Your solution is not optimal, because: 1) you have to create dedicated constructors for your concrete Shapes and you loose the type check (at compile time) of the parameters. 2) The init method of the concrete factory is error prone.

Here's what I would do. The concrete factory should carry the parameters of the concrete Shapes constructors, but not as indetermined strings (if you get strings from user input, convert them before the creation of the concrete factory):

public interface ShapeFactory {
    public Shape make(String shapeType);
}

public class ShapeFactoryImpl implements ShapeFactory {
    private int circleRadius;
    private int rectangleLength;
    private int rectangleBreadth;

    public ShapeFactoryImpl(int circleRadius, int rectangleLength, int rectangleBreadth){
        this.circleRadius = circleRadius;
        this.rectangleLength = rectangleLength;
        this.rectangleBreadth = rectangleBreadth;
    }

    public Shape make(String shapeType) {
        switch (shapeType) {
            case "Circle": return new Circle(this.circleRadius); 
            case "Rectangle": return new Rectangle(this.rectangleLength, this.rectangleBreadth);
            default: throw new Exception("..."); 
        }
    }
}

The client does not need to know the concrete ShapeFactory he is using, nor has to worry about the concrete Shape he gets. The dependency is inverted: abstractions, not details, play the key role. But if the number of possible shapes increases, you'll get a constructor with a lot of similar parameters. Here's another solution:

public class ShapeFactoryImpl implements ShapeFactory {
    private Shape circle;
    private Shape rectangle;

    public ShapeFactoryImpl(Circle circle, Rectangle rectangle){
        this.circle = circle;
        this.rectangle = rectangle;
    }

    public Shape make(String shapeType) {
        switch (shapeType) {
            case "Circle": return this.circle.clone(); 
            case "Rectangle": return this.rectangle.clone();
            default: throw new Exception("..."); 
        }
    }
}

This is better, because you won't mix parameters: each concrete Shape contains its own parameters. If you want to make it more flexible, you can use a Map to move the reponsibility for the switch out of the concrete factory:

public class ShapeFactoryImpl implements ShapeFactory {
    private Map<String, Shape> shapeByType;

    public ShapeFactoryImpl(Map<String, Shape> shapeByType){
        this.shapeByType = shapeByType;
    }

    public Shape make(String shapeType) {
        Shape shape = this.shapeByType.get(Type).clone();
        if (shape == null) {
            throw new Exception("...");
        }
        return shape;
    }
}

I would even use an enum for shape types instead of a string, and an EnumMap to handle the switch:

public class ShapeFactoryImpl implements ShapeFactory {
    private EnumMap<ShapeType, Shape> shapeByType;

    public ShapeFactoryImpl(Map<ShapeType, Shape> shapeByType){
        this.shapeByType = shapeByType;
    }

    public Shape make(ShapeType shapeType) {
        return this.shapeByType.get(Type).clone();
    }
}

The client has to know the Shape and ShapeFactory interface and the ShapeType enum. The "server" provides the concrete ShapeFactoryImpl instance.

Upvotes: 2

Related Questions