팩토리 메서드 패턴이란? Node.js와 함께 배우는 구현과 활용법

2025. 2. 12. 20:05Tech Insights/Design Pattern

반응형

팩토리 메서드 패턴이란?

팩토리 메서드 패턴(Factory Method Pattern)객체 생성 로직을 캡슐화하여 하위 클래스에서 생성 방식을 정의할 수 있도록 하는 디자인 패턴입니다.
이 패턴을 사용하면 클라이언트 코드가 객체의 구체적인 생성 방식에 의존하지 않고, 유연하게 확장할 수 있습니다.

팩토리 메서드 패턴의 특징

  1. 객체 생성 로직 캡슐화: 객체를 직접 생성하는 대신, 팩토리 메서드를 통해 생성하도록 함
  2. OCP(Open-Closed Principle) 준수: 새로운 객체를 추가해도 기존 코드 수정 없이 확장 가능
  3. 상속을 통한 확장성 증가: 부모 클래스를 수정하지 않고도 새로운 객체 유형을 추가할 수 있음

팩토리 메서드 패턴이 사용되는 경우

사용 사례설명

데이터 변환 외부 API 데이터를 내부 형식으로 변환
데이터베이스 연결 데이터베이스 드라이버를 유연하게 선택
로깅 시스템 다양한 로깅 전략을 적용 가능
UI 컴포넌트 생성 특정 UI 요소를 동적으로 생성

팩토리 메서드 패턴 구현 방법

1️⃣ 기본적인 팩토리 메서드 패턴 (TypeScript & Node.js)

// Product 인터페이스
interface Product {
    use(): void;
}

// ConcreteProductA
class ConcreteProductA implements Product {
    use(): void {
        console.log("Product A 사용됨");
    }
}

// ConcreteProductB
class ConcreteProductB implements Product {
    use(): void {
        console.log("Product B 사용됨");
    }
}

// Creator (팩토리 클래스)
abstract class Creator {
    abstract factoryMethod(): Product;

    someOperation(): void {
        const product = this.factoryMethod();
        console.log("팩토리 메서드에서 제품을 생성했습니다.");
        product.use();
    }
}

// ConcreteCreatorA
class ConcreteCreatorA extends Creator {
    factoryMethod(): Product {
        return new ConcreteProductA();
    }
}

// ConcreteCreatorB
class ConcreteCreatorB extends Creator {
    factoryMethod(): Product {
        return new ConcreteProductB();
    }
}

// 사용 예제
const creatorA = new ConcreteCreatorA();
const creatorB = new ConcreteCreatorB();
creatorA.someOperation();
creatorB.someOperation();

팩토리 메서드 패턴의 한계와 해결 방법

❌ 문제점: 부모 클래스 의존성 증가

실무에서 팩토리 메서드 패턴을 적용했을 때, 부모 클래스가 너무 많은 클래스에서 참조되면서 변경이 어려워지는 문제가 발생할 수 있습니다.
이를 해결하기 위해 몇 가지 방법을 적용할 수 있습니다.


해결 방법 1: 팩토리 메서드 패턴 내에서 DI 활용

팩토리 메서드 패턴 내에서 DI(의존성 주입)를 활용하면 부모 클래스의 변경 부담을 줄이고, 유연하게 객체를 생성할 수 있습니다.

// DataTransformer 인터페이스
interface DataTransformer {
    transform(data: any): any;
}

// 팩토리를 통한 DI 적용
class ExternalAPITransformerA implements DataTransformer {
    transform(data: any): any {
        return { id: data.userId, name: data.fullName };
    }
}

class ExternalAPITransformerB implements DataTransformer {
    transform(data: any): any {
        return { id: data.identifier, name: data.username };
    }
}

// TransformerFactory 인터페이스
interface TransformerFactory {
    createTransformer(): DataTransformer;
}

// 구체적인 팩토리 구현
class APIATransformerFactory implements TransformerFactory {
    createTransformer(): DataTransformer {
        return new ExternalAPITransformerA();
    }
}

class APIBTransformerFactory implements TransformerFactory {
    createTransformer(): DataTransformer {
        return new ExternalAPITransformerB();
    }
}

// DI를 활용한 변환 서비스
class DataConverter {
    private transformer: DataTransformer;
    constructor(factory: TransformerFactory) {
        this.transformer = factory.createTransformer();
    }
    convert(data: any): any {
        return this.transformer.transform(data);
    }
}

// 사용 예제
const converterA = new DataConverter(new APIATransformerFactory());
const converterB = new DataConverter(new APIBTransformerFactory());
console.log(converterA.convert({ userId: 123, fullName: "John Doe" }));
console.log(converterB.convert({ identifier: "abc-456", username: "Alice" }));
팩토리 메서드 패턴 내에서 DI(의존성 주입)를 활용하여 유연한 변환 로직을 적용할 수 있음

해결 방법 2: 팩토리 인터페이스 활용

팩토리 클래스를 인터페이스로 분리하면 부모 클래스의 변경 부담을 줄일 수 있습니다.

interface TransformerFactory {
    createTransformer(): DataTransformer;
}

class APIATransformerFactory implements TransformerFactory {
    createTransformer(): DataTransformer {
        return new ExternalAPITransformerA();
    }
}

class APIBTransformerFactory implements TransformerFactory {
    createTransformer(): DataTransformer {
        return new ExternalAPITransformerB();
    }
}

function processData(factory: TransformerFactory, data: any) {
    const transformer = factory.createTransformer();
    console.log("변환된 데이터:", transformer.transform(data));
}

const apiAData = { userId: 123, fullName: "John Doe" };
const apiBData = { identifier: "abc-456", username: "Alice" };
processData(new APIATransformerFactory(), apiAData);
processData(new APIBTransformerFactory(), apiBData);
팩토리 메서드 패턴을 유지하면서 부모 클래스 의존성을 줄일 수 있음

결론: 팩토리 메서드 패턴을 언제 사용해야 할까?

  • 새로운 객체를 추가해도 기존 코드를 변경하지 않아야 할 때
  •  객체 생성 방식이 복잡하고 다양한 변형이 필요할 때
  •  특정 객체가 인터페이스에 의존하며 확장성이 필요한 경우

그러나, 부모 클래스의 의존성이 높아지면 DI 주입 또는 팩토리 인터페이스화를 고려하는 것이 좋다.

팩토리 메서드 패턴을 실제 적용할 때, 확장성과 유지보수성을 함께 고려해야 한다는 점을 염두에 두는 것이 좋습니다.

반응형