Factory pattern with typescript

Exploring the Factory Pattern in TypeScript: How to create flexible and maintainable user systems with a focus on extensibility and maintainability

August 29, 2024


타입스크립트(TypeScript)에서 팩토리 패턴(Factory Design Pattern)을 활용하여 유연하고 확장 가능한 사용자 시스템을 구현하는 방법을 살펴보겠습니다. 이 접근법은 다양한 유형의 사용자들을 체계적으로 관리할 수 있는 확장 가능하고 유지 보수하기 쉬운 코드를 작성하는 데 유용합니다. 각 구성 요소를 단계별로 살펴보겠습니다.

문제 정의

여러분이 무료(Free), 체험판(Trial), 프리미엄(Premium)과 같은 서로 다른 유형의 사용자가 있는 애플리케이션을 구축하고 있다고 상상해 보세요. 각 사용자 유형은 고유한 권한과 동작을 가지고 있습니다. 우리는 각 사용자 유형의 로직을 별도의 클래스에 캡슐화하여 코드베이스를 깔끔하고 체계적으로 유지하고 싶습니다. 또한, 사용자의 유형에 따라 이 클래스 인스턴스를 동적으로 생성할 수 있는 방법이 필요합니다.

사용자 유형을 위한 열거형(Enum) 정의

먼저, UserKind라는 열거형(enum)을 정의하여 서로 다른 사용자 유형을 나타냅니다:

enum UserKind {
  FREE,
  TRIAL,
  PREMIUM,
}

열거형은 이름이 지정된 상수 집합을 정의하는 데 매우 유용하며, 코드의 가독성을 높이고 실수를 줄일 수 있습니다.

사용자 타입 정의

다음으로, 사용자 객체의 기본 구조를 나타내는 TUser 타입을 정의합니다. 여기에는 id, name, type이 포함됩니다:

type TUser = {
  id: string;
  name: string;
  type: UserKind;
};

이 타입을 통해 사용자 객체를 생성할 때 일관된 구조를 유지할 수 있습니다.

추상 사용자 클래스

모든 사용자 유형에 공통적인 동작을 캡슐화하기 위해 User라는 추상 클래스를 정의합니다:

abstract class User {
  id: TUser['type'];

  abstract read(): string;
  abstract create(): string;

  protected constructor(id: TUser['type']) {
    this.id = id;
  }
}

User 클래스는 각 구체적인 사용자 유형이 구현할 두 가지 추상 메소드 read()와 create()를 가집니다. id 속성은 사용자의 유형을 나타내며 생성자를 통해 설정됩니다.

구체적인 사용자 클래스

그런 다음, Premium, Free, Trial 세 가지 구체적인 클래스를 생성하여 User 클래스를 확장합니다:

class Premium extends User {
  constructor() {
    super(UserKind.PREMIUM);
  }

  create(): string {
    return '프리미엄 사용자는 콘텐츠를 생성할 수 있습니다.';
  }

  read(): string {
    return '프리미엄 사용자는 모든 콘텐츠를 읽을 수 있습니다.';
  }
}

class Free extends User {
  constructor() {
    super(UserKind.FREE);
  }

  create(): string {
    return '무료 사용자는 콘텐츠를 생성할 수 없습니다.';
  }

  read(): string {
    return '무료 사용자는 제한된 콘텐츠를 읽을 수 있습니다.';
  }
}

class Trial extends User {
  constructor() {
    super(UserKind.TRIAL);
  }

  create(): string {
    return '체험판 사용자는 제한된 콘텐츠를 생성할 수 있습니다.';
  }

  read(): string {
    return '체험판 사용자는 제한된 콘텐츠를 읽을 수 있습니다.';
  }
}

각 클래스는 사용자 유형의 권한에 따라 create()와 read() 메소드를 구현합니다.

UserFactory 클래스

사용자 유형의 인스턴스를 동적으로 생성하기 위해 UserFactory 클래스를 구현합니다:

class UserFactory<T = User> {
  private readonly userClass: new (...args: any) => T;

  constructor(userClass: new (...args: any) => T) {
    this.userClass = userClass;
  }

  create(user: TUser) {
    return new this.userClass(user);
  }
}

UserFactory 클래스는 생성자 파라미터로 사용자 클래스를 받아 이를 userClass라는 private 변수에 저장합니다. create() 메소드는 new 키워드를 사용해 해당 사용자 클래스를 인스턴스화합니다.

사용자 생성하기

마지막으로, UserFactory를 사용하여 서로 다른 사용자 유형의 인스턴스를 생성하는 방법을 살펴보겠습니다:

const p = new UserFactory(Premium);
p.create({ id: 'premium', name: 'premium user', type: UserKind.PREMIUM });

const f = new UserFactory(Free);
f.create({ id: 'free', name: 'free name', type: UserKind.FREE });

여기서는 Premium과 Free 사용자 클래스의 인스턴스를 생성하고 있습니다. 팩토리 패턴을 사용하면 UserFactory 생성자에 다른 클래스를 전달하여 생성할 사용자 클래스를 쉽게 변경할 수 있습니다.

팩토리 디자인 패턴은 타입스크립트 애플리케이션에서 다양한 유형의 사용자들을 관리하는 강력한 방법을 제공합니다. 생성 로직을 추상화하고 동작을 개별 클래스에 캡슐화함으로써 코드를 더 모듈화하고 유지보수가 용이하게 만들 수 있으며, 향후 확장을 위한 준비도 할 수 있습니다. 이 접근 방식은 다양한 사용자 역할을 관리해야 하고 각기 다른 권한과 기능이 필요한 애플리케이션에 특히 유용합니다.