Builder pattern
The builder pattern is a way to build complex objects in programming without mixing up the steps involved in creating them. It helps keep things organized by separating the making of the object from what the object is supposed to be like. This method is one of the Gang of Four design patterns, which are famous for making programming easier to manage and understand.
I am going to show you two ways of doing it, one is the most seen out there, and the other one we can describe as "Java-like" which I like much more than the first one because it helps to keep the code altogether
let's give an example of an Error DTO, that we can use in the real world in an API for example.
First approach: most seen
interface IBuilder {
withStatusCode(code: number): this;
withMessage(msg: string): this;
withTimestamp(timestamp: string): this;
withPath(path: string): this;
build(): ErrorDto
}
class Builder implements IBuilder {
private errorDto: ErrorDto;
constructor() {
this.errorDto = new ErrorDto();
}
withStatusCode(code: number) {
this.errorDto.statusCode = code;
return this;
}
withMessage(msg: string) {
this.errorDto.message = msg;
return this;
}
withTimestamp(timestamp: string) {
this.errorDto.timestamp = timestamp;
return this;
}
withPath(path: string) {
this.errorDto.path = path;
return this;
}
build() {
return this.errorDto;
}
}
class ErrorDto {
statusCode: number;
message: string;
timestamp: string;
path: string;
static builder() {
return new Builder();
}
}
const errorDto: ErrorDto = ErrorDto.builder()
.withStatusCode(200)
.withMessage("Success")
.withTimestamp(new Date().toISOString())
.withPath("/builder/pattern")
.build();
However, I believe that having many classes and interfaces can be somewhat uncomfortable. That is why I prefer the second approach, which I call 'Java-like'. This method encapsulates the builder class within the object itself.
Second approach: Java-like
class ErrorDto {
statusCode: number;
message: string;
timestamp: string;
path: string;
static builder() {
class Build {
private errorDto: ErrorDto = new ErrorDto();
withStatusCode(code: number) {
this.errorDto.statusCode = code;
return this;
}
withMessage(msg: string) {
this.errorDto.message = msg;
return this;
}
withTimestamp(timestamp: string) {
this.errorDto.timestamp = timestamp;
return this;
}
withPath(path: string) {
this.errorDto.path = path;
return this;
}
build() {
return this.errorDto;
}
}
return new Build()
}
}
const errorDto: ErrorDto = ErrorDto.builder()
.withStatusCode(200)
.withMessage("Success")
.withTimestamp(new Date().toISOString())
.withPath("/builder/pattern")
.build();
This is how we can implement the builder pattern in TypeScript—simple, yet elegant.
If you found this article useful, consider exploring my other posts.