OCP를 지키는 디자인 패턴, 팩토리 패턴

푸드테크·2022년 7월 10일
1

안녕하세요 푸드 테크팀 백엔드 개발자 박형민입니다

오늘은 디자인 패턴 중, 생성 패턴에 해당하는 팩토리 패턴에 대해서 포스팅을 해보고자 합니다.

팩토리 패턴이란,

팩토리 패턴은, 인스턴스를 만드는 절차를 추상화는 방법 주 하나로, 객체를 생성하는 인터페이스를 미리 정의하되, 어떤 인스턴스로 생성할지 클래스를 결정하는 것은, 하위 클래스에서 결정할 수 있도록, 결정을 미루는 생성 패턴입니다.

즉, 여러개의 하위 클래스(혹은 구현체)를 가지는 상위 클래스(혹은 인터페이스) 가 있을 때, 들어오는 Input값에 따라 어떤 인스턴스를 생성해줄지 리턴해주는 결정할 수 있는 방법입니다.




팩토리 패턴이 유용하게 사용될 때


  1. 어떤 클래스가 자신이 생성해야 하는 객체의 클래스인지 예측할 수 없을 떄
  2. 생성할 객체를 결정하는 책임을 하위 클래스에게 떠넘기고 싶을 때

즉, 유연하게 객체의 역할을 결정하고 싶을 때 팩토리패턴을 사용할 수 있다고 생각됩니다.


팩토리 패턴을 사용한다면, 구현체 혹은 하위 클래스로 새로운 역할을 가지는 클래스를 생성해 줄 수 있기 때문에, 추가적인 요구사항이 생기더라도,

새로운 구현체 하나만 생성해주면 되기 때문에, 변경에는 닫혀있고 확장에는 열리있는 객체지향의 개방-폐쇄 원칙을 잘 지킬수 있는 방법이기도 합니다.




예제

팩토리 패턴에 대한 예제로, 간단한 심플 팩토리를 구현해 보겠습니다.

요구사항
1. 클라이언트가 차를 구매합니다.
2. 클라이언트는 차의 종류를 선택할 수 있습니다.
3. 차 공장에서는, 클라이언트의 주문에 따른 차를 만들어준다.



먼저 Car라는 공통된 기능을 가지는 인터페이스를 만들어줍니다.

public interface Car {
    String toEngine();
    int setSeat();
}

Car의 공통된 기능을 가지는, Car 구현체들을 만들어 줍니다.
public class OldCar implements Car{
    @Override
    public String toEngine() {
        return "올드카는 부르르릉 브르릉";
    }

    @Override
    public int setSeat() {
        int oldCar_seat = 4;
        return oldCar_seat;
    }
}


public class SuperCar implements Car{
    @Override
    public String toEngine() {
        return "슈퍼카는 부아아아아ㅏㅏㅏㅏ앙";
    }

    @Override
    public int setSeat() {
        int superCar_seat = 2;
        return superCar_seat;
    }
}

Car를 만들어주는 공장을 만들어줍니다. (팩토리)

public class CarFactory {
    public Car getCar(String car){
        return choose(car);
    }

    private Car choose(String car){
        switch (car) {
            case "old":
                return new OldCar();
            case "super":
                return new SuperCar();
        }
        return null;
    }
}

자 이제, 유저가 Car를 선택하면, 어떤 확인해봅니다.

public class Client {
    public static void main(String[] args) {
        CarFactory carFactory = new CarFactory();
        
        Car car1 = carFactory.getCar("old");
        System.out.println("car1.toEngine() = " + car1.toEngine());
        System.out.println("car1.setSeat() = " + car1.setSeat());
        
        Car car2 = carFactory.getCar("super");
        System.out.println("car2.toEngine() = " + car2.toEngine());
        System.out.println("car2.setSeat() = " + car2.setSeat());
    }
}



클라이언트쪽을 보면, 유저는 선택하고(파라미터) 이 선택에 의해, 어떤 객체가 생성되는지가 유동적으로 결정됩니다.



팩토리 패턴의 장점

즉, 컴파일 시 객체의 생성이 결정되는 게 아닌, 런타임 시 클라이언트의 요구와 상황에 따라 어떤 객체를 생성하고, 어떤 역할을 수행할 것인지 선택을 뒤로 미룰 수 있습니다.

또한,

  1. Car의 종류가 늘어난다고 하더라도, 구현체만 계속 만들어주면 되기에, 기존의 oldcar나, supercar 혹은 그 외의 코드에 대한 영향도를 최대한 줄일 수 있는 확장에 유용한 구조가 됩니다.

  2. 클라이언트 코드로부터 인스턴스화를 제거함으로써, 객체간의 종속성을 줄이고, 결합도를 느슨하게 하는 구조가 됩니다.

  3. 팩토리 패턴은 클라이언트와 구현 객체들 사이에 추상화를 제공하기 때문에 공통된 부분을 추출하는데, 용이합니다.




이번 포스팅에서는 디자인 패턴 중, 팩토리 패턴에 대해서 간단하게 알아보았습니다.

  • 팩토리 패턴은, 디지안패턴인 만큼, 이를 구현하는데 정답은 없다고 생각합니다.

  • 위의 코드처럼 간단하게 switch 문으로 객체를 생성해 줄 수도 있고,
    Map을 사용하거나, 메소드를 넘겨받거나, 추상 팩토리를 구현하거나 방법은 상황과 조건에 따라 바뀔 수 있으나,

팩토리 패턴의 주요목적은, 클라이언트로 부터 객체 생성을 캡슐화하고, 객체간의 종속성을 낮추고 확장에 유용하게 객체를 생성하는 구조를 가지는 것이라고 생각합니다!

profile
푸드 테크 기술 블로그

1개의 댓글

comment-user-thumbnail
2022년 7월 18일

글 잘 봤습니다. 의존성을 역전실킬수 있어 팩토리 패턴을 많이 사용할수 있을거 같아요!

답글 달기