티스토리 뷰

생성 패턴 중에서 많이 쓰이는 팩토리 패턴(Factory Pattern)에 대해 알아보겠습니다.

 

 

1. 팩토리 패턴(Factory Pattern)의 개념 및 역할


[ 팩토리 패턴이란? ]

팩토리 패턴(Factory Pattern)은 객체를 생성하는 디자인 패턴 중 하나입니다. 객체를 생성하는 과정을 캡슐화하여 클라이언트가 직접 객체를 생성하는 것이 아니라 생성 로직을 분리해서 객체를 요청하고 생성하는 디자인 패턴입니다. 이를 통해 객체 생성의 유연성을 높이고, 의존성을 줄이며, 코드의 확장성을 향상시킵니다.

 

[ 팩토리 패턴 주요 4가지 역할 ]

1. 팩토리 패턴을 사용하여 생성 과정을 캡슐화할 수 있다. 이때 생성 과정의 변경 사항은 호출자에게 투명성을 가진다.
2. 생성 과정을 팩터리 클래스로 추출한 후, 재사용할 수 있다.
3. 복잡한 생성 과정을 캡슐화하므로, 호출자는 객체 생성 방법을 알 필요가 없다.
4. 생성 과정과 사용 과정을 분리하여 복잡한 코드를 간결하게 바꿀 수 있다.

 

 

 

2. 팩토리 패턴 종류


[ 팩토리 패턴 종류 ]

종류로는 크게 세 가지가 있습니다.
1. 단순 팩토리 패턴(Simple Factory Pattern)
2. 팩토리 메소드 패턴(Factory Method Pattern)
3. 추상 팩토리 패턴(Abstract Factory Pattern)

 

 

단순 팩토리 패턴(Simple Factory Pattern)

단순 팩토리 패턴은 하나의 팩토리 클래스가 다양한 객체를 생성하는 것을 다룹니다.
클라이언트는 생성할 객체의 타입을 팩토리에게 전달하고, 팩토리는 그에 해당하는 객체를 반환합니다.

 

예를 들어, 회원이 상품을 구매할  구매 조건으로 뱃지 제한, 중복 구매 제한이 있다고 가정하겠습니다. 
먼저, 인터페이스를 정의합니다.

interface PurchaseCondition {
    void verify(Member member);
}

 

 

그런 다음, 구매 조건에 따라 다른 종류의 상품을 생성하는 구체적인 클래스를 구현합니다.

public class PurchaseConditionBadge implements PurchaseCondition {
    @Override
    public void verify(Member member) {...}
}

public class PurchaseConditionCountLimit implements PurchaseCondition {
    @Override
    public void verify(Member member) {...}
}

 

 

이제 객체들을 생성하고 관리하는 팩토리를 만듭니다. 

public class PurchaseConditionFactory {
    public static PurchaseCondition get(PurchaseConditionType type) {
    	if (type == BADGE) {
            return new PurchaseConditionBadge();
        } else if (type == COUNT_LIMIT) {
            return new PurchaseConditionCountLimit();
        } 
        return type;
    }
}

public class PurchaseConditionService {
    public void verify(PurchaseConditionType type, Member member) {
    	PurchaseCondition purchaseCondition = PurchaseConditionFactory.get(type);
        purchaseCondition.verify(member);
    }
}

 

 

이렇게 생성되는 클래스는 단순 팩토리 패턴이 적용된 클래스입니다. 클라이언트는 팩토리에게 구매 조건의 타입을 전달하고, 팩토리는 해당하는 구매 조건 객체를 생성하여 반환합니다. 앞에서 본 단순 팩토리 패턴의 코드를 보면 PurchaseConditionFactory의 get( ) 함수가 호출될 때마다 새 purchaseCondition 객체가 생성된다. 사실 이 purchaseCondition 객체를 재사용할 수 있다면 메모리와 객체가 생성의 부담을 줄이기 위해 미리 purchaseCondition 객체를 생성하고 캐시할 수 있다. 구체적인 코드는 다음과 같다.

public class PurchaseConditionFactory {
    private static final Map<PurchaseConditionType, PurchaseCondition> purchaseConditionMap = new HashMap<>();
    static {
        purchaseConditionMap.put(BADGE, new PurchaseConditionBadge());
        purchaseConditionMap.put(COUNT_LIMIT, new PurchaseConditionCountLimit());
    }

    public static PurchaseCondition get(PurchaseConditionType type) {
        return purchaseConditionMap.get(type);
    }
}

 

이렇게 하면 미리 캐시된 purchaseConditionMap에서 객체를 가져와서 사용할 수 있다. 
만약 새로운 구매 제한이 추가되면 PurchaseConditionFactory 클래스 코드가 변경될 수 밖에 없다. 이는 개방 폐쇄 원칙에 위배될 수 있지만, 새로운 purchaseCondition을 빈번하게 추가하지 않고 PurchaseConditionFactory 클래스의 코드를 가끔씩만 수정한다면 개방 폐쇄 원칙을 완전히 만족하지 않아도 괜찮다.

 

 

 

팩토리 메소드 패턴(Factory Method Pattern)

메소드 팩토리 패턴은 객체 생성의 인터페이스를 정의하지만, 실제 어떤 클래스의 인스턴스를 만들지는 서브클래스에서 결정하도록 만드는 패턴입니다. 이를 통해 객체 생성의 책임을 서브클래스로 분리할 수 있습니다.

 

예를 들어, 회원 등급에 따른 할인 적용을 가정하겠습니다.
일단 할인 적용을 위해 일반과 실버 등급을 구체적인 클래스에 구현한다.

public interface Discount {
    double applyDiscount(double price);
}

public class RegularDiscount implements Discount {
    @Override
    public double applyDiscount(double price) {
        return price; // 일반 회원은 할인 없음
    }
}

public class SilverDiscount implements Discount {
    @Override
    public double applyDiscount(double price) {
        return price * 0.9; // 실버 회원은 10% 할인
    }
}

 

 

그리고 각 회원 타입에 따라 등급 할인 객체를 생성한다.
createDiscount 추상 팩토리 메서드를 정의하여 서브 클래스에서 적합한 메소드를 결정한다.

public abstract class Member {
    public double buy(double price) {
        Discount discount = createDiscount();
        return discount.applyDiscount(price);
    }

    protected abstract Discount createDiscount();
}

public class RegularMember extends Member {
    @Override
    protected Discount createDiscount() {
        return new RegularDiscount();
    }
}

public class SilverMember extends Member {
    @Override
    protected Discount createDiscount() {
        return new SilverDiscount();
    }
}

 

 

회원 등급에 따라 적절한 Member 객체를 생성하는 팩토리 클래스를 만듭니다.

public class MemberFactory {
    public static final HashMap<String, Member> map = new HashMap<>();
    static {
        map.put("REGULAR", new RegularMember());
        map.put("SILVER", new SilverMember());
    }

    public static Member get(String memberType) {
        return map.get(memberType);
    }
}

이렇게 각 객체의 생성 논리가 복잡한 경우 팩토리 메소드 패턴을 사용하여 여러 객체 간에 구성이 단순하게 되는 것을 볼 수 있다.
이처럼 객체 생성의 책임을 서브 클래스에 위임할 수 있어 코드의 확정성과 유연성을 높일 수 있습니다. 만약 새로운 회원 타입을 추가하고 싶다면 새로운 서브 클래스를 작성하면 됩니다.

 

 

 

추상 팩토리 패턴(Abstract Factory Pattern)

추상 팩토리 패턴은 여러 종류의 관련된 객체를 생성하는 팩토리를 제공합니다. 이 패턴은 팩토리를 추상화하여, 클라이언트가 인터페이스를 통해 여러 종류의 객체를 생성할 수 있도록 합니다. 예를 들어, GUI 애플리케이션에서 서로 다른 스타일의 GUI 컴포넌트(예: Window, Mac)를 생성하는 경우 추상 팩토리 패턴을 사용할 수 있습니다.

interface Button {
    void render();
}

interface Checkbox {
    void render();
}

class WindowsButton implements Button {
    @Override
    public void render() {...}
}

class WindowsCheckbox implements Checkbox {
    @Override
    public void render() {...}
}

class MacOSButton implements Button {
    @Override
    public void render() {...}
}

class MacOSCheckbox implements Checkbox {
    @Override
    public void render() {...}
}

interface GUIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

class WindowsGUIFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

class MacOSGUIFactory implements GUIFactory {
    @Override
    public Button createButton() {
        return new MacOSButton();
    }

    @Override
    public Checkbox createCheckbox() {
        return new MacOSCheckbox();
    }
}

추상 팩토리 패턴은 이런 특수한 경우를 위해 만들어진 패턴이다. 만약 WindowGUI 한 가지 유형만 만드는 거 였다면 단순 팩토리 패턴이나 팩토리 메소드 패턴으로 구현할 수 있지만 여러가지 유형의 객체를 만들어야 한다면 추상 팩토리 패턴을 사용해서 클래스의 수를 효과적으로 줄일 수 있다. 

 

 

[ 팩토리 패턴 종류의 용도와 장단점 ]

  용도 장점 단점
단순 팩토리 패턴 객체의 생성 과정이 간단할 경우
클래스의 인스턴스가 몇 개 없는 경우
객체 생성 로직이 중앙화하여 코드 수정 범위 최소화 클래스가 증가할수록 복잡도 향상
팩토리 메소드 패턴 서브클래스에서 객체 생성 방식을 결정할 경우 확장성, 코드 재사용성, 유연성 향상 서브클래스를 많이 만들게 되면 코드 관리가 어려워짐
추상 팩토패턴 여러 유형의 객체를 합성 또는 초기화 작업이 필요한 경우 관련된 객체들을 일관된 방식으로 생성할 수 있어 일관성을 유지 구현이 복잡하고 코드가 길어짐

 

'∙디자인패턴' 카테고리의 다른 글

[디자인패턴] 전략 패턴 사용하기  (0) 2022.11.19