Description
I want to create an instance of an object at runtime, however, the parameters used to create the object are not always the same. To help explain, I have created a simplified example (in TypeScript).
In this example, I want to create a instance of a Rectangle
class at runtime. Depending on where in the code the rectangle is created, a different set of parameters may be used to create the rectangle.
Initial Solution
Initially, I created a single factory class that was responsible for creating a rectangle. This class can then be injected as needed to create a rectangle at runtime.
Code
Point
export class Point {
private readonly _x: number;
private readonly _y: number;
public constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
public get x(): number {
return this._x;
}
public get y(): number {
return this._y;
}
}
Rectangle
export class Rectangle {
private readonly _width: number;
private readonly _height: number;
public constructor(width: number, height: number) {
this._width = width;
this._height = height;
}
public get width(): number {
return this._width;
}
public get height(): number {
return this._height;
}
}
Rectangle Factory
export class RectangleFactory {
public createFromWidthAndHeight(width: number, height: number): Rectangle {
return new Rectangle(width, height);
}
public createFromPoints(p1: Point, p2: Point): Rectangle {
let width: number = Math.abs(p1.x - p2.x);
let height: number = Math.abs(p1.y - p2.y);
if (width == 0 || height == 0) return null;
return new Rectangle(width, height);
}
public createFromRectangle(r: Rectangle): Rectangle {
return new Rectangle(r.width, r.height);
}
}
Should I want to create a rectangle some new way, like from edges or given a width and an area, etc... I would need to continue expanding this factory class, which violates SOLID and could lead to quite a lengthy class.
Improved Solution?
An approach I took to make the factory more open/closed was to create a parameter object (or interface), which has the option of storing any parameter that may be used by a factory method to create the rectangle (IRectangleFactoryInput
).
Parameter Object
export interface IRectangleFactoryInput {
width?: number;
height?: number;
p1?: Point;
p2?: Point;
rectangle?: Rectangle;
}
Then I created a rectangle factory interface, which takes the parameter object and returns a rectangle.
Rectangle Factory Interface
export interface IRectangleFactory {
create(input: IRectangleFactoryInput): Rectangle
}
Finally, I created separate classes for each method used to create a rectangle. With this approach, if I need to create a rectangle some other way, I would add parameters to the parameter object, and create a new factory class that implements the IRectangleFactory
interface. The IRectangleFactory
can be injected as needed.
Rectangle from Width and Height
export class RectangleFromWidthAndHeight implements IRectangleFactory {
create(input: IRectangleFactoryInput): Rectangle {
if (input.width == null || input.height == null) return;
return new Rectangle(input.width, input.height);
}
}
Rectangle from Points
export class RectangleFromPoints implements IRectangleFactory {
create(input: IRectangleFactoryInput): Rectangle {
if (input.p1 == null || input.p2 == null) return
let width: number = Math.abs(input.p1.x - input.p2.x);
let height: number = Math.abs(input.p1.y - input.p2.y);
if (width == 0 || height == 0) return null;
return new Rectangle(width, height);
}
}
Rectangle from Rectangle
export class RectangleFromRectangle implements IRectangleFactory {
create(input: IRectangleFactoryInput): Rectangle {
if (input.rectangle == null) return;
return new Rectangle(input.rectangle.width, input.rectangle.height);
}
}
I understand this approach would potentially lead to a rather large parameter object. But I'm thinking that would be far less messy than a rather large factory class.
Questions
- Is there a better/cleaner way to go about creating instances dynamically using a factory?
- Are there any major drawbacks / flaws with my "improved approach"?