DesignPatterns

팩토리 메서드란? (Factory Method)

Tommy__Kim 2023. 6. 15. 17:17

의도

팩터리 메서드는 생성과 관련된 디자인 패턴입니다.
팩터리 메서드는 상위 클래스에서 객체를 생성하기 위한 인터페이스를 제공하지만 생성될 객체의 유형은 하위 클래스가 변경을 할 수 있도록 허용하는 구조입니다.

문제 상황

예를 들어 어떠한 물건을 배달하는 코드가 있다고 가정해 보겠습니다.
처음에는 장사가 잘 되지 않아 오로지 트럭으로만 배달을 시작했습니다.

그런데 어느 순간 저희가 운영하는 앱의 사용자가 많아져 선박, 비행기로도 전송을 시작하려고 합니다.
사업적인 측면에서 너무 좋은 일입니다.
하지만 개발자인 저희는?
기존에 배달과 관련하여 Truck으로만 구현되어 있던 코드들을 수정해야 할 것입니다.
또 추후에 근거리 지역의 경우 개인 자가용으로 배달을 진행한다고 합니다.
이럴 때 개발자인 저희는?
기존의 코드들을 또 손 봐야 합니다.

해결 방법

앞서 살펴본 문제상황을 자세히 들여다보면, 새로운 운송 수단 (새로운 객체)가 추가될 때마다 코드를 수정해야 한다는 문제점이 있습니다.
객체 생성의 책임이 한 곳에 몰려 있기 때문에 문제가 발생하고 있습니다.
이러한 생성의 책임을 인터페이스화 하고 이를 구현한 클래스에서 생성의 책임을 지도록 하면 어떨까요?
이러한 원리를 적용한 것이 팩토리 메서드입니다.

팩토리 메서드의 구조

위 그림에서 Creator클래스의 createProduct() 메서드가 Product를 생성하는 책임을 지니고 있습니다.
Creator 클래스를 구현한 ConcreteCreator에서 createProduct()를 오버라이딩 하여 원하는 Product를 생성하고 있습니다.

예시

팩토리 메서드 적용 전

public class TransportFactory {

    void transport(String vehicleType, String name, String address) {
        validate(name, address);
        Vehicle vehicle = chooseVehicle(vehicleType);
        doTransport(name, address, vehicle);
    }

    private void validate(String name, String address) {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("주문자 성함을 기입해주세요");
        }
        if (address == null || address.isBlank()) {
            throw new IllegalArgumentException("주소를 남겨주세요");
        }
    }
    // 운송 수단이 추가 될 때 마다 코드의 변경이 발생
    Vehicle chooseVehicle(String vehicleType){
        if (vehicleType.equals("Truck")) {
            return new Truck();
        }
        if (vehicleType.equals("Airplane")) {
            return new Airplane();
        }
        throw new IllegalArgumentException("지원하지 않는 운송수단입니다.");
    }


    private void doTransport(String name, String address, Vehicle vehicle){
        System.out.println("고객님 성함 : " + name + " | 운송 방법 : " + vehicle.getType() + " | 운송 주소 : " + address);
    };


}

팩토리 메서드를 적용하기 전 코드를 살짝 살펴보도록 하겠습니다.
transport 메서드에서 vehicleType이라는 매개변수를 받고 있습니다.
매개변수의 값에 따라 조건에 맞는 Vehicle을 반환하고 있습니다.
만약 새로운 운송수단이 생긴다면, chooseVehicle 메서드에 새로운 코드가 추가될 것입니다. (OCP 위반)

팩토리 메서드 적용 후

public interface TransportFactory {

    default void transport(String name, String address) {
        validate(name, address);
        Vehicle vehicle = chooseVehicle();
        doTransport(name, address, vehicle);
    }

    private void validate(String name, String address) {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("주문자 성함을 기입해주세요");
        }
        if (address == null || address.isBlank()) {
            throw new IllegalArgumentException("주소를 남겨주세요");
        }
    }

    Vehicle chooseVehicle();

    private void doTransport(String name, String address, Vehicle vehicle){
        System.out.println("고객님 성함 : " + name + " | 운송 방법 : " + vehicle.getType() + " | 운송 주소 : " + address);
    };


}

앞서 팩토리 메서드에서 살펴봤던 createProduct() 부분이 chooseVechile() 메서드입니다.
인터페이스를 보면 운송 수단(Vehicle)을 제외한 모든 부분들이 구현되어 있습니다.

@Getter
@Setter
@ToString
public class Vehicle {
    private String type;    // 운송 수단 종류
    private String color;   // 색상
}

Vehicle의 경우 간단한 정보만을 담고 있습니다.

만약 트럭을 생성하고자 하면 다음과 같이 생성하면 됩니다.

public class TruckTransportFactory implements TransportFactory{
    @Override
    public Vehicle chooseVehicle() {
        return new Truck();
    }
}

--- 

public class Truck extends Vehicle {
    public Truck() {
        setType("트럭");
        setColor("파랑");
    }
}

TruckTransportFactory는 TransportFactory를 implements 한 뒤 Vehicle 생성하는 로직만 구현해 주면 됩니다.

만약 비행기를 생성하고자 하면 다음과 같이 생성하면 됩니다.

public class AirplaneTransportFactory implements TransportFactory{
    @Override
    public Vehicle chooseVehicle() {
        return new Airplane();
    }
}

--- 

public class Airplane extends Vehicle {
    public Airplane() {
        setType("비행기");
        setColor("파란색");
    }
}

AirplaneTransportFactory도 마찬가지로 Vehicle을 생성하는 로직만 구현해 주면 됩니다.

객체 생성의 책임을 하위 클래스에게 넘겨줌으로 인해 새로운 운송 수단이 생길 때마다, 기존 코드의 변경 없이 확장을 할 수 있습니다.

장단점

[장점]

  • creator와 product 사이의 강한 결합을 막을 수 있다.
  • Single Responsibility Principle을 지킬 수 있다.
    • 객체의 생성 책임을 하위 클래스에게 전달함으로 이뤄낼 수 있었다.
  • Open Closed Principle을 지킬 수 있다.
    • 새로운 객체를 생성할 때에는 하위 클래스를 새로 생성하기 때문에 OCP원칙을 준수하고 있다.

[단점]

  • 팩토리 메서드를 사용하기 전보다는 코드가 복잡해진다.

'DesignPatterns' 카테고리의 다른 글

[디자인 패턴] 추상팩토리 (Abstract Factory)  (0) 2023.06.27