CS 면접 바이블: Chapter 1 (2)

정지인·2026년 8월 7일
post-thumbnail

이 내용은 지난 장의 내용을 이어서 정리한 내용이다.


1.1.2 팩토리 패턴

팩토리 패턴(Factory Pattern)은 객체를 사용하는 코드와 객체를 생성하는 코드를 분리하는 디자인 패턴이다.

즉, 객체를 직접 생성하지 않고 별도의 팩토리를 통해 객체를 생성하도록 구성한다.

특히 상속 관계를 이용하는 팩토리 메서드 패턴에서는

  • 상위 클래스가 객체 생성의 전체적인 구조를 정의
  • 하위 클래스가 실제로 어떤 객체를 생성할지 결정

하는 방식으로 구성된다.

객체를 사용하는 코드
        ↓
     팩토리
        ↓
어떤 객체를 만들지 결정
        ↓
구체적인 객체 생성

상위 클래스와 구체적인 객체 생성 로직이 분리되므로 서로 간의 의존성이 줄어들고 느슨한 결합(Loose Coupling)을 만들 수 있다.


상위 클래스

상위 클래스는 객체 생성 과정의 전체적인 뼈대를 담당한다.

  • 구체적으로 어떤 클래스의 인스턴스를 생성하는지 알 필요가 없음
  • 객체 생성 방식이 변경되더라도 사용하는 코드의 변경을 최소화할 수 있음
  • 객체 생성 로직을 한곳에서 관리할 수 있어 유지보수가 쉬움
  • 새로운 객체 종류가 추가되어도 기존 코드에 미치는 영향을 줄일 수 있음

예를 들어 커피를 만드는 시스템이라면 상위 클래스는 커피를 주문받고 생성하는 전체적인 과정을 담당한다.


하위 클래스

하위 클래스는 실제로 어떤 객체를 생성할지 결정하고 구체적인 생성 로직을 구현한다.

예를 들어 다음과 같이 나눌 수 있다.

LatteFactory
    ↓
Latte 생성

EspressoFactory
    ↓
Espresso 생성

즉,

  • 상위 클래스 → 객체 생성의 전체적인 구조
  • 하위 클래스 → 실제로 생성할 객체와 구체적인 생성 방법

을 담당한다.


커피 공장으로 이해하기

커피 공장에 비유하면 다음과 같다.

커피 주문
   ↓
CoffeeFactory
   ↓
어떤 커피인가?
   ↓
 ┌───────────────┐
 │               │
LatteFactory   EspressoFactory
 │               │
Latte 생성     Espresso 생성

CoffeeFactory는 전체 생산 공정을 관리하고, LatteFactory, EspressoFactory는 각각 어떤 커피를 실제로 만들 것인지 결정한다.


자바스크립트에서 객체 생성

자바스크립트의 Object 생성자는 전달받은 값에 따라 서로 다른 Wrapper 객체를 생성할 수 있다.

const num = new Object(42)
const str = new Object('abc')

console.log(num.constructor.name) // Number
console.log(str.constructor.name) // String

숫자를 전달하면 Number 객체가 생성되고, 문자열을 전달하면 String 객체가 생성된다.

즉, 입력값에 따라 생성되는 객체의 타입이 달라질 수 있다는 점에서 팩토리와 비슷한 객체 생성 방식을 확인할 수 있다.

다만 이것을 일반적인 의미의 팩토리 메서드 패턴 자체라고 보는 것은 정확하지 않고, 객체 생성 로직을 내부에서 결정하는 예시 정도로 이해하는 것이 좋다.


커피 팩토리 예제

class CoffeeFactory {
  static createCoffee(type) {
    const factory = factoryList[type]
    return factory.createCoffee()
  }
}

class Latte {
  constructor() {
    this.name = "latte"
  }
}

class Espresso {
  constructor() {
    this.name = "espresso"
  }
}

class LatteFactory extends CoffeeFactory {
  static createCoffee() {
    return new Latte()
  }
}

class EspressoFactory extends CoffeeFactory {
  static createCoffee() {
    return new Espresso()
  }
}

const factoryList = {
  LatteFactory,
  EspressoFactory
}

const main = () => {
  const coffee = CoffeeFactory.createCoffee("LatteFactory")

  console.log(coffee.name) // latte
}

main()

전체 흐름은 다음과 같다.

CoffeeFactory.createCoffee("LatteFactory")
                ↓
factoryList에서 LatteFactory 선택
                ↓
LatteFactory.createCoffee()
                ↓
new Latte()
                ↓
Latte 객체 반환

CoffeeFactory는 어떤 팩토리를 사용할지 결정하는 역할을 하고, LatteFactoryEspressoFactory는 실제 객체 생성 방법을 담당한다.

따라서 객체를 사용하는 쪽에서는 직접 다음과 같이 작성할 필요가 없다.

const coffee = new Latte()

대신 팩토리를 통해 객체를 생성한다.

const coffee = CoffeeFactory.createCoffee("LatteFactory")

이를 통해 객체 생성 로직과 객체를 사용하는 로직을 분리할 수 있다.


static 메서드

CoffeeFactorycreateCoffee()static으로 선언되어 있다.

static createCoffee(type) {
  ...
}

정적 메서드는 클래스의 인스턴스를 생성하지 않고도 클래스 자체에서 직접 호출할 수 있다.

CoffeeFactory.createCoffee("LatteFactory")

따라서 다음과 같이 객체를 먼저 생성할 필요가 없다.

const factory = new CoffeeFactory()
factory.createCoffee(...)

즉, 이 예제에서 static을 사용하는 핵심 이유는 팩토리 객체를 별도로 생성하지 않고 객체 생성 기능을 바로 사용할 수 있도록 하기 위함이다.

static 메서드의 핵심은 클래스의 인스턴스가 아니라 클래스 자체에 속한 메서드라는 점이다.


의존성 주입과의 차이

팩토리 패턴과 의존성 주입(Dependency Injection)은 서로 관련될 수 있지만 동일한 개념은 아니다.

팩토리 패턴은

객체를 어떻게 생성할 것인가

에 초점을 둔다.

반면 의존성 주입은

객체가 필요로 하는 다른 객체를 외부에서 어떻게 전달할 것인가

에 초점을 둔다.

따라서 위 예제는 기본적으로 팩토리를 이용한 객체 생성 패턴으로 보는 것이 적절하며, 단순히 factoryList를 사용했다는 이유만으로 의존성 주입이라고 보기는 어렵다.


자바의 팩토리 패턴

enum CoffeeType {
    LATTE,
    ESPRESSO
}

abstract class Coffee {
    protected String name;

    public String getName() {
        return name;
    }
}

class Latte extends Coffee {
    public Latte() {
        name = "Latte";
    }
}

class Espresso extends Coffee {
    public Espresso() {
        name = "Espresso";
    }
}

class CoffeeFactory {
    public static Coffee createCoffee(CoffeeType type) {
        switch (type) {
            case LATTE:
                return new Latte();
            case ESPRESSO:
                return new Espresso();
            default:
                throw new IllegalArgumentException("Invalid coffee type: " + type);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Coffee coffee = CoffeeFactory.createCoffee(CoffeeType.LATTE);
        System.out.println(coffee.getName()); // Latte
    }
}
  • Coffee → 공통 상위 클래스
  • Latte, Espresso → 실제 생성되는 객체
  • CoffeeFactory → 전달받은 타입에 따라 적절한 객체 생성
  • Main → 객체를 직접 new하지 않고 팩토리를 통해 생성
CoffeeType.LATTE
      ↓
CoffeeFactory
      ↓
new Latte()
      ↓
Latte 객체 반환

객체 생성 로직을 CoffeeFactory에 분리하여 객체를 사용하는 코드와 생성하는 코드의 결합도를 낮춘다.

※ 이 코드는 엄밀히 말하면 Simple Factory 형태에 가깝다.

Enum

  • enum은 서로 관련된 상수들을 하나의 타입으로 묶어 관리하는 자료형이다.
  • 정해진 값 중 하나만 사용할 수 있어 잘못된 값이 들어가는 것을 방지할 수 있다.
    ex) LATTE, ESPRESSO처럼 선택 가능한 종류가 제한된 경우에 사용한다.

profile
인천대학교 멋쟁이사자 13기 백엔드 & 14기 백엔트 파트장 및 부회장

0개의 댓글