Reputation: 297
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
Reputation: 8180
Your solution is not optimal, because: 1) you have to create dedicated constructors for your concrete Shape
s 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 Shape
s 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