프로그래밍/알고리즘&자료구조&패턴

디자인패턴 - 팩토리패턴

lazy_web_devloper 2025. 8. 18. 10:49
728x90
반응형

JAVA로 구현하는 알고리즘과 디자인 패턴 공부: 팩토리 패턴 (Factory)

1. 팩토리 패턴이란? 🏭

개념: 객체를 생성하는 부분을 별도의 클래스(Factory)로 캡슐화하여, 클라이언트 코드가 구체적인 클래스 이름에 의존하지 않고 객체를 생성할 수 있도록 만드는 패턴입니다.

마치 커피숍에서 손님이 "커피 주세요"라고 주문하면, 바리스타(Factory)가 주문에 맞춰 에스프레소, 라떼, 아메리카노 중 하나를 만들어주는 것과 같습니다. 손님은 어떤 커피 머신(구체적인 클래스)이 사용되는지 알 필요 없이, 오직 '커피'라는 인터페이스에만 의존하면 됩니다.

2. 왜 사용할까요?

  • 결합도 감소: 객체를 사용하는 코드(클라이언트)와 객체를 생성하는 코드를 분리(Decoupling)할 수 있습니다. 나중에 새로운 종류의 커피(클래스)가 추가되더라도, 바리스타(Factory)의 코드만 수정하면 되고 손님(클라이언트) 코드는 전혀 변경할 필요가 없습니다.
  • 유연성 및 확장성: 새로운 하위 클래스가 추가되어도 전체 코드의 수정을 최소화할 수 있어 시스템 확장이 매우 유용합니다.
  • 책임 분리: 객체 생성의 복잡한 로직을 모두 팩토리 클래스가 책임지므로, 클라이언트 코드는 자신의 핵심 로직에만 집중할 수 있습니다.

3. Java로 구현하는 방법

팩토리 패턴은 주로 팩토리 메서드 패턴추상 팩토리 패턴으로 나뉩니다. 여기서는 가장 보편적인 **팩토리 메서드 패턴(Factory Method Pattern)**을 중심으로 알아보겠습니다.

시나리오: 다양한 종류의 Notification(알림) 객체(SMS, Email, Push)를 생성하는 시스템을 만들어 보겠습니다.

1단계: 공통 인터페이스(또는 추상 클래스) 정의

먼저 모든 알림 객체가 구현해야 할 공통 규약을 만듭니다.

Java
 
// 모든 알림 객체가 가져야 할 공통 인터페이스
public interface Notification {
    void sendNotification();
}

2단계: 구체적인 클래스(Product) 구현

공통 인터페이스를 구현하는 실제 알림 객체들을 만듭니다.

Java
 
// SMS 알림 구현체
public class SmsNotification implements Notification {
    @Override
    public void sendNotification() {
        System.out.println("SMS 알림을 보냅니다!");
    }
}

// Email 알림 구현체
public class EmailNotification implements Notification {
    @Override
    public void sendNotification() {
        System.out.println("Email 알림을 보냅니다!");
    }
}

// Push 알림 구현체
public class PushNotification implements Notification {
    @Override
    public void sendNotification() {
        System.out.println("Push 알림을 보냅니다!");
    }
}

3단계: 팩토리 클래스 구현

이제 이 객체들을 생성해주는 '공장'을 만듭니다. 이 팩토리는 요청에 따라 적절한 알림 객체를 생성하여 반환합니다.

Java
 
public class NotificationFactory {
    // 이 메서드가 바로 '팩토리 메서드' 입니다.
    // 입력된 타입에 따라 다른 객체를 생성하여 반환합니다.
    public Notification createNotification(String channel) {
        if (channel == null || channel.isEmpty()) {
            return null;
        }
        switch (channel) {
            case "SMS":
                return new SmsNotification();
            case "EMAIL":
                return new EmailNotification();
            case "PUSH":
                return new PushNotification();
            default:
                throw new IllegalArgumentException("알 수 없는 채널입니다: " + channel);
        }
    }
}

4단계: 클라이언트 코드에서 팩토리 사용

클라이언트 코드는 이제 new SmsNotification()처럼 구체적인 클래스 이름을 몰라도 됩니다. 오직 팩토리를 통해 원하는 객체를 얻기만 하면 됩니다.

Java
 
public class Main {
    public static void main(String[] args) {
        // 1. 팩토리 인스턴스 생성
        NotificationFactory factory = new NotificationFactory();

        // 2. 팩토리를 통해 "SMS" 타입의 Notification 객체 요청
        // 클라이언트는 SmsNotification 클래스의 존재를 몰라도 된다.
        Notification sms = factory.createNotification("SMS");
        sms.sendNotification(); // 출력: SMS 알림을 보냅니다!

        // 3. "EMAIL" 타입 객체 요청
        Notification email = factory.createNotification("EMAIL");
        email.sendNotification(); // 출력: Email 알림을 보냅니다!

        // 만약 새로운 '카카오톡' 알림이 추가된다면?
        // NotificationFactory와 KakaoNotification 클래스만 추가/수정하면 되고,
        // 이 Main 클래스(클라이언트)는 전혀 수정할 필요가 없다!
    }
}

4. 팩토리 패턴과 스프링 (Spring Framework)

스프링 프레임워크의 심장부인 **BeanFactory**와 이를 상속받는 **ApplicationContext**는 팩토리 패턴의 정수라고 할 수 있습니다.

스프링 컨테이너 자체가 바로 거대한 팩토리입니다.

  • BeanFactory: 이름 그대로 빈(Bean) 객체를 생성하고 관리하는 공장입니다. 우리가 @Component나 XML 설정을 통해 빈을 등록하면, BeanFactory는 이 정보를 바탕으로 필요할 때 해당 빈의 인스턴스를 생성하여 제공합니다.
  • getObject(): BeanFactory의 핵심 메서드로, 빈의 이름을 전달하면 해당 빈 객체를 반환해주는 역할을 합니다. 이는 위 예제의 createNotification() 메서드와 정확히 같은 역할을 수행합니다.

우리가 스프링을 사용하면서 new 키워드를 거의 사용하지 않을 수 있는 이유가 바로 이 팩토리 패턴 덕분입니다. 스프링이라는 거대한 팩토리가 모든 객체 생성의 책임을 대신 져주기 때문이죠.


5. 문제 제시 및 답변

개념을 잘 이해했는지 확인해볼 시간입니다! 💡

문제: 게임을 개발하고 있습니다. 사용자가 캐릭터를 선택하면, Warrior, Mage, Archer 등 다양한 직업의 캐릭터 객체를 생성해야 합니다. 게임이 업데이트되면서 앞으로 Thief, Healer 같은 새로운 직업이 계속 추가될 예정입니다.

이때, 캐릭터를 생성하는 로직에 팩토리 패턴을 적용하면 어떤 장점이 있을까요? 클라이언트 코드(예: 캐릭터 선택 화면)는 어떻게 변할까요?

답변:

네, 이런 시나리오에 팩토리 패턴을 적용하는 것은 매우 이상적입니다.

장점:

  1. 확장성: 새로운 직업 Thief가 추가될 때, Thief 클래스를 만들고 CharacterFactory에 case "THIEF":만 한 줄 추가하면 됩니다. 캐릭터를 선택하고 사용하는 클라이언트 코드(캐릭터 선택 화면 등)는 전혀 건드릴 필요가 없습니다. 이는 유지보수 비용을 극적으로 줄여줍니다.
  2. 캡슐화: 캐릭터 생성 시 필요한 복잡한 초기화 로직(예: 전사는 기본 방패 아이템 지급, 마법사는 기본 스태프 지급 등)을 모두 CharacterFactory 안에 숨길 수 있습니다. 클라이언트 코드는 이런 복잡한 과정을 알 필요 없이 깔끔하게 유지됩니다.

클라이언트 코드의 변화:

  • 적용 전:
  • Java
     
    // 직업 선택에 따라 분기
    if (job.equals("Warrior")) {
        character = new Warrior();
    } else if (job.equals("Mage")) {
        character = new Mage();
    } // 새로운 직업이 추가될 때마다 이 코드는 계속 길어진다...
    
  • 적용 후:클라이언트 코드가 훨씬 단순해지고, 새로운 직업이 추가되어도 이 코드는 절대 변하지 않습니다.
  • Java
     
    // 팩토리에 직업 이름만 알려주면 끝.
    CharacterFactory factory = new CharacterFactory();
    Character character = factory.createCharacter(job);
    
728x90
반응형