팩토리 메서드 패턴

  • 부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하고 자식 클래스들이 생성될 객체들의 유형을 변경할 수 있도록 하는 패턴

연습 문제 코드부터 보겠습니다. 아래 내용과 요구사항을 보고 직접 리팩토링 해보세요.

public class LogisticsService {
    public void planDelivery(String type) {
        if (type.equals("truck")) {
            Truck truck = new Truck();
            System.out.println("배송 준비 중...");
            truck.deliver();
            System.out.println("배송 완료!");
        } else if (type.equals("ship")) {
            Ship ship = new Ship();
            System.out.println("배송 준비 중...");
            ship.deliver();
            System.out.println("배송 완료!");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        LogisticsService service = new LogisticsService();
        service.planDelivery("truck");
    }
}

public class Ship {
    public void deliver() {
        System.out.println("선박으로 해상 배송합니다. 📦🚢");
    }
}

public class Truck {
    public void deliver() {
        System.out.println("트럭으로 육로 배송합니다. 📦🚚");
    }
}

팩토리 메서드 패턴의 구조 및 요구사항

Creator 클래스의 주책임은 제품을 생성하는 것이 아니라 제품과 관련된 공통된 핵심 비즈니스 로직이 있고 그 사이에 제품을 생성하는 메서드를 넣음으로서 공통된 핵심 비즈니스 로직을 하나로 모으는 것이 팩토리 메서드 패턴의 포인트라고 할 수 있습니다

Creator 클래스 = Logistics 클래스 생성 (abstract)
ConcreteProduct 클래스 = Ship, Truck
ConcreteCreator 클래스 = Logistics를 상속받는 클래스
Product 클래스 = Ship Truck이 해당 인터페이스를 구현해야함

팩토리 메서드의 예시

public abstract class Logistics {

    // 팩토리 메서드
    abstract Transport createTransport();

    // 공통 로직 (템플릿 메서드)
    public void planDelivery() {
    }

요구사항

  • 템플릿 메서드(공통 로직)는 상속받은 구체 클래스들이 공통적으로 사용해야 하기에 abstract 메서드를 안에서 사용한다.
  • 생성 메서드는 구체 클래스들이 각각 특정 객체를 생성하여 반환할 수 있도록 하는 abstract 메서드이다.
  • 생성되어야 하는 객체는 같은 인터페이스를 구현해야한다.
  • 팩토리 메서드는 항상 새 인스턴스를 생성할 필요 없이 캐시된 객체를 반환할 수도 있다. (하나의 ConcreteCreator 클래스에 캐싱을 적용해보세요)

정답

  • Logistics 구체 클래스로 분기를 나누었고 템플릿 메서드 안에 배송 시작과 배송 완료를 넣음으로써 복잡한 if문을 단순화 시킨 것을 확인 할 수 있습니다.
  • if문이 복잡하다면 concurrentHashMap으로 초기화 시킨 후 getOrDefault로 기본값(DefaultLogistics)을 반환할 수 있습니다
  • RoadLogistics는 객체 생성시 새 객체를 생성할 수도 있고 기존 객체를 재사용할 수도 있게 만들었습니다.
public class Main {
    public static void main(String[] args) {
        String type = "truck";

        Logistics logistics = new DefaultLogistics();

        if (type.equals("truck")) {
            logistics = new RoadLogistics();
        } else if (type.equals("ship")) {
            logistics = new SeaLogistics();
        }
        logistics.planDelivery();
    }
}

public abstract class Logistics {

    // 팩토리 메서드
    abstract Transport createTransport();

    // 공통 로직 (템플릿 메서드)
    public void planDelivery() {
        System.out.println("배송 준비 중...");
        Transport transport = createTransport();
        transport.deliver();
        System.out.println("배송 완료!");
    }

}

// 알 수 없는 타입에 대한 기본값 처리
public class DefaultLogistics extends Logistics{
    @Override
    Transport createTransport() {
        return new DefaultTransport();
    }
}

public class RoadLogistics extends Logistics {
    private Truck cachedTruck;

    @Override
    public synchronized Transport createTransport() {
        if (cachedTruck == null) {
            cachedTruck = new Truck();
        }

        return cachedTruck;
    }
}

public class SeaLogistics extends Logistics {
    @Override
    public Transport createTransport() {
        return new Ship();
    }
}

public interface Transport {
    void deliver();
}

public class Truck implements Transport {
    public void deliver() {
        System.out.println("트럭으로 육로 배송합니다. 📦🚚");
    }
}

public class Ship implements Transport {
    public void deliver() {
        System.out.println("선박으로 해상 배송합니다. 📦🚢");
    }
}

public class DefaultTransport implements Transport{
    @Override
    public void deliver() {
        System.out.println("기본 제품입니다");
    }
}

 

concurrentHashMap 예시

public class Main {
    private static final Map<String, Logistics> LOGISTICS_MAP = new ConcurrentHashMap<>();

    static {
        LOGISTICS_MAP.put("truck", new RoadLogistics());
        LOGISTICS_MAP.put("ship", new SeaLogistics());
    }

    public static Logistics create(String type) {
        return LOGISTICS_MAP.getOrDefault(type, new DefaultLogistics());
    }

    public static void main(String[] args) {
        String type = "truck";

        Logistics logistics = LOGISTICS_MAP.get(type);
        logistics.planDelivery();
    }
 }

 

 


참조
https://refactoring.guru/ko/design-patterns/factory-method

 

+ Recent posts