Reputation: 484
I'm trying to implement the Builder Patten similar to Java in TypeScript 4.6.2. From what I have read, this isn't the preferred way to do it, but I have some constrains regarding the module exports.
export class HttpRequest {
static Builder = class {
private body: string;
setBody(body: string): HttpRequest.Builder {
this.body = body;
return this;
}
build(): HttpRequest {
return new HttpRequest(this);
}
}
private readonly body: string;
constructor(builder: any) {
this.body = builder.body;
}
getBody(): string {
return this.body;
}
}
It seems that the setBody
method can't return a HttpRequest.Builder
type, with the following error:
TS2702: 'HttpRequest' only refers to a type, but is being used as a namespace here.
It seems to work fine if // @ts-ignore
is present before the method declaration, or if the method signature is changed to setBody(body: string): any
.
Is there any way to implement this with nested classes? I'm trying to avoid having two different classes, one for HttpRequest
and one for Builder
.
Upvotes: 3
Views: 1151
Reputation: 314
In my experience using a fluent API has served me best when writing typescript builders, here's how I would do it in more of a typescript way.
interface HttpRequest {
body: string;
}
export class HttpRequestBuilder {
private body: string = "";
public withBody(newBody: string): HttpRequestBuilder {
this.body = newBody;
return this;
}
public build(): HttpRequest {
return {
body: this.body
}
}
}
Then to use this you can just new up the class
const httpRequest = new HttpRequestBuilder()
.withBody("add a body here")
.build();
This pattern also has several other options to be extended further.
Update: here is a hypothetical example extension to give you even more versatility.
export interface HttpRequest {
body: string;
contentType: string;
}
export interface HttpRequestExtension extends HttpRequest {
message: string;
}
export class HttpRequestBuilder {
private body: string = "";
private contentType: string = "";
public withBody(newBody: string): HttpRequestBuilder {
this.body = newBody;
return this;
}
public withContentType(newContentType: string): HttpRequestBuilder {
this.contentType = newContentType;
return this;
}
public build(): HttpRequest {
return {
body: this.body,
contentType: this.contentType
}
}
}
export class HttpRequestExtensionBuilder extends HttpRequestBuilder {
private message: string = "";
public withMessage(newMessage: string): HttpRequestExtensionBuilder {
this.message = newMessage;
return this;
}
public build(): HttpRequestExtension {
return {
...super.build(),
message: this.message
}
}
}
This way we can use either builder:
const httpRequest = new HttpRequestBuilder()
.withBody("add a body here")
.withContentType("text/html")
.build();
const httpRequest = new HttpRequestExtensionBuilder()
.withMessage("add a message here")
.withBody("add a body here")
.withContentType("text/html")
.build();
Upvotes: 1
Reputation: 729
I wouldn't prefer it to use builder with static classes. You can refer here to access a playground template.
If you want to achieve it in your way do this:
class HttpRequest {
static Builder = class {
public body!: string;
setBody(body: string): typeof HttpRequest.Builder {
this.body = body;
return this as any;
}
build(): HttpRequest {
return new HttpRequest(this);
}
}
private readonly body: string;
constructor(builder: any) {
this.body = builder.body;
}
getBody(): string {
return this.body;
}
}
But instead you can practice full pattern with:
interface IHttpRequest
{
setRequest(body:any):void
}
class HttpRequest implements IHttpRequest
{
body!:string;
setRequest(body:string){
console.log('hook: setRequest called')
this.body = body;
}
}
interface IHttpBuilder{
buildRequest(body:string): void
}
class HttpBuilder implements IHttpBuilder{
private http = new HttpRequest();
buildRequest(body:string){
console.log('hook: buildRequest called')
this.http.setRequest(body);
}
}
class Basement{
private http!: IHttpBuilder;
constructor(http:IHttpBuilder){
this.http = http;
}
build(body:string){
console.log('hook: build called')
this.http.buildRequest(body);
}
}
let httpReq = new HttpBuilder();
let builder = new Basement(httpReq);
let body = "i am body!";
builder.build(body);
Upvotes: 1