Course content
Factory Method: Centralise Shape Creation
Easy·Tagsdesign-patternscreationalfactory-method
Problem Statement
The starter has a small `Shape` hierarchy with three concrete subclasses (`Circle`, `Square`, `Rectangle`) and a `ShapeFactory` whose `create` method is unimplemented:
```java
public static Shape create(String type, double... dimensions) {
return null;
}
```
Without a working factory, every part of the codebase that needs a shape has to know about every concrete subclass and write its own type-based dispatch:
```java
Shape s;
if (type.equals("circle")) s = new Circle(radius);
else if (type.equals("square")) s = new Square(side);
else if (type.equals("rectangle")) s = new Rectangle(width, height);
else throw new IllegalArgumentException("Unknown shape: " + type);
```
Duplicate that block across renderer code, importer code, API code, and you have several places that all need to be updated whenever a new shape type is added.
Implement `ShapeFactory.create(String type, double... dimensions)` so that there is exactly one place in the codebase that knows how to construct shapes by name. Callers ask the factory for a shape, supply the dimensions, and get back a `Shape` reference. Adding a new shape type later becomes a one-file change.
After your refactor, the `create` method must satisfy this contract:
- `create("circle", radius)` returns a `Circle` constructed with the given radius.
- `create("square", side)` returns a `Square` constructed with the given side length.
- `create("rectangle", width, height)` returns a `Rectangle` constructed with the given width and height.
- For any other type string, the method throws `IllegalArgumentException` with a message that names the unrecognised type.
- The declared return type is `Shape` (the abstraction). Callers depend only on `Shape`, never on a specific concrete subclass.
The validator runs five checks:
1. `create("circle", 5)` returns a `Circle` whose `area()` matches the formula for radius 5.
2. `create("square", 4)` returns a `Square` whose `area()` is 16.
3. `create("rectangle", 3, 5)` returns a `Rectangle` whose `area()` is 15.
4. `create("triangle", 1)` throws `IllegalArgumentException`. Unknown types must be rejected explicitly, not return null silently.
5. `ShapeFactory.create`'s declared return type is `Shape`. Reflection check, mirrors the contract that callers depend on the abstraction.
Examples
Example 1
Input
ShapeFactory.create("circle", 5)Output
"a Shape whose area() is 25π"Why
The factory builds a Circle with radius 5 and returns it as a Shape reference.
Example 2
Input
ShapeFactory.create("rectangle", 3, 5)Output
"a Shape whose area() is 15"Why
Rectangle takes two dimensions: width and height.
Example 3
Input
ShapeFactory.create("hexagon", 6)Output
"throws IllegalArgumentException"Why
Unknown type strings are rejected with a message that names the unrecognised type.
Constraints
- •ShapeFactory.create must accept a String type and a double... varargs of dimensions.
- •ShapeFactory.create must return Shape (the abstract supertype), not a concrete subclass.
- •ShapeFactory.create must throw IllegalArgumentException for any type string it does not recognise.
- •The four classes must keep their names: Shape, Circle, Square, Rectangle, ShapeFactory.
- •Type-based branching must live in ShapeFactory only. Callers should not need to know which concrete subclass a Shape ultimately is.
Hints
Stuck? Reveal a nudge toward the right pattern, one step at a time.
Hint 1
A switch statement on the type string is the simplest implementation. case "circle": return new Circle(dimensions[0]); and so on.
Hint 2
Each shape's constructor needs different arguments. Read the docstring on ShapeFactory.create to see which index of dimensions[] each shape consumes.
Hint 3
Add a default branch that throws IllegalArgumentException with a message that includes the offending type, for example `throw new IllegalArgumentException("Unknown shape type: " + type);`. Returning null is not enough.
Hint 4
The declared return type stays Shape. Inside the switch you allocate concrete classes (new Circle, new Square, ...) but the method signature exposes only the abstraction. Callers receive a Shape reference and never need to cast.
Hint 5
If you find yourself writing the same switch in a caller class, that is the smell the factory exists to solve. Move the dispatch into ShapeFactory and have the caller depend on Shape only.