Simple Factory

2023. 12. 30. 14:05Server/디자인패턴

도입

new 연산자

new 연산자를 사용하면, 구상 클래스의 인스턴스가 만들어진다. 구상은 추상의 반댓말로서, 인터페이스가 아닌 특정한 구현을 사용하는 방식이다.

하지만 객체지향 방식의 개발에서는 구상에 집중하지 않는다. 구상 클래스를 바탕으로 진행하는 코딩은 후에 코드를 수정해야 할 가능성이 커지고, 유연성이 떨어진다.

 

Duck duck = new MallardDuck(); 이전에 작성한 오리 인터페이스를 이용한 코드 스니펫이다.

보다싶이 Duck 인터페이스를 사용했지만, 그럼에도 new 연산자를 통해 결국에는 구상 클래스의 인스턴스를 만들어야 한다.

이것은 다음과 같은 코드 구현으로 이어진다.

Duck duck;
if(picnic){
    duck = new MallardDuck();
}else if(hunting){
    duck = new DecoyDuck();
}else if(inBathTub){
    duck = new RubberDuck();
}

Duck 이 무엇을 의미하는지는 모르지만, 인터페이스임을 짐작할 수 있다. 또한 상황에 따라 picnic, hunting, inBathTub 등 상황에 따라 if-else 문을 통해 인스턴스를 직접 지정하고있다.

이는 컴파일 하기 전까지 어떤 것의 인스턴스를 만들어야 하는지 알 수 없음을 뜻한다.

그럼 New 가 문제인가?


Simple Factory 

사실 New 는 예약어므로 문제가 아니다. 문제는 ‘변화하는 어떤것’ 이다. 인터페이스에 맞춰 코딩하게 된다면 여러가지 변화에 대응할 수 있게 된다. 하지만 구상 클래스를 많이 사용한다면, 변화가 생길 때 마다 코드를 수정해야 하므로 문제의 여지가 많다.

이는 객체지향 원칙인 SOLID 중 하나인 Open Closed Principle (폐쇄-개방 원칙) 에 해당한다.

그럼 어떻게 수정할 수 있을까 ?

Pizza orderPizza(){
    Pizza pizza = new Pizza();

    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}

다음과 같은 행동들이 구현된 피자를 주문하는 코드가 있다고 하자.

이 세상에 피자는 하나가 아니다. 따라서 여러 종류의 피자를 주문할 수 있어야 한다.

Pizza orderPizza(String type){
    Pizza pizza;

    if(type.equals("cheese")){
        pizza = new CheesePizza();
    } else if(type.equals("greek")){
        pizza = new GreekPizza();
    } else if(type.equals("pepperoni"){
        pizza = new PepperoniPizza();
    }

    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}

코드가 조금 수정되었다. 매개변수 type에 따라서 동적으로 pizza를 생성할 수 있게 되었다.

이 코드에서 발생할 수 있는 문제점은 무엇일까 ?

바로 새로운 피자가 추가될 때 마다, OrderPizza 의 코드를 일일히 수정해야 한다는 점이다. 잘 와닿지 않는다면, 이러한 요구사항의 변경이 있을 수 있겠다.

“greek 피자는 이제 팔지 않고, 그대신 새로운 불고기 피자가 추가되었습니다. “

그러면 우리는 orderPizza 의 코드를

if(type.equals("cheese")){
        pizza = new CheesePizza();
    //} else if(type.equals("greek")){
    //pizza = new GreekPizza();
    } else if(type.equals("pepperoni"){
        pizza = new PepperoniPizza();
    } else if(type.equals("bulgogi"){
        pizza = new BulgogiPizza();
    }

이렇게 또 한번 바꿔줘야 한다.

이러한 요구사항의 변경은 흔하게 일어나는 일이다. 하지만 변경에 따라 일일히 관계 없는 피자의 생성 부분을 수정해야 한다면, 오류 발생의 소지가 증가하고, 관리도 힘들어지게 된다.

이러한 부분에 착안해, 우리는 Pizza 의 생성을 담당하는 팩토리를 만들 수 있다.

public class SimplePizzaFactory{

    public Pizza createPizza(String type){
        Pizza pizza = null;

        if(type.equals("cheese")){
            pizza = new CheesePizza();
        } else if(type.equals("greek")){
            pizza = new GreekPizza();
        } else if(type.equals("pepperoni"){
            pizza = new PepperoniPizza();
        }
        return pizza;
    }
}

또한 이러한 변경점을 클라이언트에도 적용시켜야 한다.

public class PizzaStore{

    SimplePizzaFactory factory;

    public PizzaStore(SimplePizzaFactory factory){
        this.factory = factory;
    }

    public Pizza orderPizza(String type){

        Pizza pizza;
        pizza = factory.createPizza(type);

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    //기타 메소드

}

이런 방식으로 피자 생성을 SimplePizzaFactory 에서 관리함으로서, orderPizza 메소드는 자신의 역할인 피자 주문에 집중하고, 구상클래스의 인스턴스를 직접 만들 필요가 없게 된다.

이러한 방식을 Simple Factory 기법이라 칭한다.

하지만 Simple Factory 는 디자인 패턴이라기 보다는 프로그래밍에 자주 쓰이는 관용구에 가깝다.

개인적인 생각으로는 OCP를 해결하기 보다는 다른 객체에 위임하는 역할 밖에 못하기 때문이라고 생각한다.

이제 다음 포스팅에서 여러가지 팩토리 패턴을 알아보도록 하자 !

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

Factory Method Pattern  (0) 2023.12.30