Skip to main content

생성패턴:빌더

많은 필드와 중첩된 객체들을 만들어야 하는 문제.

  • 복잡한 객체를 단계별로 생성할 수 있도록 한다.
  • 집을 짓는다고 생각하면 된다.
    • buildWall
    • buildDoors
    • buildWindows
    • buildRoof
    • buildGarage *build함수는 this가 리턴되어, 체이닝연산이 가능하다.
    • getResult() *마지막에는 최종본이 리턴된다.

구조

Builder : 객체를 만들 수 있도록 독립된 단계를 구현한다.
Director : 빌더를 주입받아, 독립된 단계들을 바탕으로 완성본을 만들어 낸다.

사용사례

1. DTO (Data Transfer Object) 생성

대규모 폼 데이터나 여러 단계에 걸쳐 데이터를 수집하는 경우, DTO를 빌더 패턴으로 생성하면 코드를 더 깔끔하고 관리하기 쉽게 만들 수 있습니다.

아래는 중첩클래스를 사용했다. (객체와 그의 기능을 명확하게 묶음)

  • Builder는 static class를 할당받는다.
  • UesrDto 클래스안에 빌더클래스를 정의하여 연관성을 높였다.
class UserDto {
private constructor(
public readonly username: string,
public readonly email: string,
public readonly age?: number,
public readonly address?: string,
) {}

static Builder = class {
private username: string;
private email: string;
private age?: number;
private address?: string;

setUsername(username: string) {
this.username = username;
return this;
}

setEmail(email: string) {
this.email = email;
return this;
}

setAge(age: number) {
this.age = age;
return this;
}

setAddress(address: string) {
this.address = address;
return this;
}

build() {
return new UserDto(this.username, this.email, this.age, this.address);
}
}
}

// Usage
const userDto = new UserDto.Builder()
.setUsername('JohnDoe')
.setEmail('john.doe@example.com')
.setAge(25)
.build();

2. Configuration 객체 생성

NestJS에서 모듈의 설정을 빌더 패턴을 통해 유연하게 생성할 수 있습니다. 예를 들어, 데이터베이스 연결 설정을 빌더 패턴을 사용해 단계적으로 설정하고 최종적으로 DatabaseConfig 객체를 생성할 수 있습니다.

class DatabaseConfig {
private constructor(
public readonly host: string,
public readonly port: number,
public readonly username: string,
public readonly password: string,
) {}

static Builder = class {
private host: string;
private port: number;
private username: string;
private password: string;

setHost(host: string) {
this.host = host;
return this;
}

setPort(port: number) {
this.port = port;
return this;
}

setUsername(username: string) {
this.username = username;
return this;
}

setPassword(password: string) {
this.password = password;
return this;
}

build() {
return new DatabaseConfig(this.host, this.port, this.username, this.password);
}
}
}

// Usage
const dbConfig = new DatabaseConfig.Builder()
.setHost('localhost')
.setPort(5432)
.setUsername('admin')
.setPassword('secret')
.build();

3. Custom Decorator 생성

NestJS에서 커스텀 데코레이터를 정의할 때도 빌더 패턴을 사용할 수 있습니다. 복잡한 데코레이터 설정을 구성할 때 유용합니다.

function CustomDecoratorBuilder() {
let roles: string[] = [];
let permissions: string[] = [];

return {
setRoles(newRoles: string[]) {
roles = newRoles;
return this;
},
setPermissions(newPermissions: string[]) {
permissions = newPermissions;
return this;
},
build() {
return (target: any, key: string | symbol, descriptor: PropertyDescriptor) => {
Reflect.defineMetadata('roles', roles, target, key);
Reflect.defineMetadata('permissions', permissions, target, key);
};
},
};
}

// Usage
@CustomDecoratorBuilder()
.setRoles(['admin'])
.setPermissions(['read', 'write'])
.build()
class SomeService {
someMethod() {
// method implementation
}
}

4. Service Configuration

서비스 클래스의 복잡한 초기화 과정에서 빌더 패턴을 사용해 설정을 구성할 수 있습니다. 이렇게 하면 서비스의 생성자에서 많은 파라미터를 사용하는 대신, 빌더를 통해 필요한 설정을 명확하게 관리할 수 있습니다.

class MailServiceConfig {
private constructor(
public readonly apiKey: string,
public readonly domain: string,
public readonly fromEmail: string,
) {}

static Builder = class {
private apiKey: string;
private domain: string;
private fromEmail: string;

setApiKey(apiKey: string) {
this.apiKey = apiKey;
return this;
}

setDomain(domain: string) {
this.domain = domain;
return this;
}

setFromEmail(fromEmail: string) {
this.fromEmail = fromEmail;
return this;
}

build() {
return new MailServiceConfig(this.apiKey, this.domain, this.fromEmail);
}
}
}

// Usage
const mailConfig = new MailServiceConfig.Builder()
.setApiKey('your-api-key')
.setDomain('example.com')
.setFromEmail('no-reply@example.com')
.build();

이와 같은 방법으로, NestJS에서 빌더 패턴을 활용하여 복잡한 객체의 생성을 관리할 수 있습니다. 이를 통해 코드의 가독성과 유지보수성을 높일 수 있으며, 특히 다양한 설정을 유연하게 처리할 수 있습니다.