팩토리 메서드 패턴 과 전략 패턴

이원찬·2024년 5월 30일

디자인 패턴

목록 보기
1/4

간단 설명

객체 생성의 인터페이스를 정의하지만

인스턴스를 만들 클래스의 선택은 서브클래스에 맡기는 디자인 패턴!

서비스 상황 가정

Truck을 이용해 물품을 배달하는 서비스가 있다고 해보자

public class Truck {
    public void delivery() {
        System.out.println("Truck Delivery!");
    }
}
public class DeliveryService {
    public static void main(String[] args) {
        Truck truck = new Truck();
        truck.delivery();
    }
}

위 경우 트럭말고 다른 운송 체계인 Ship(배) 추가 된다고 해보자

Truck 과 결합된 모든 클래스들의 수정을 해야할 것이다.

이는 엄청난 비용이 소모될 것이다.

자바에 대해 익숙한 사람이라면 처음에 가장 생각나는 것이 추상 클래스 또는 인터페이스의 활용일 것이다.

추상클래스, 인터페이스를 이용한 수정 (전략 패턴?)

interface Transport {
    void delivery();
}

class Truck implements Transport {

    @Override
    public void delivery() {
        System.out.println("Truck Delivery!");
    }
}

class Ship implements Transport {

    @Override
    public void delivery() {
        System.out.println("Ship Delivery");
    }
}

public class DeliveryServiceInterface {
    public static void main(String[] args) {
        Transport transport = new Truck();
        transport.delivery();
    }
}

이렇게 인터페이스를 이용해 인터페이스의 메서드로 추상화가 가능하다.

Client 클래스가 있다면 이런 방식으로 transport를 사용하는 행동을 숨길수 있다.

이것을 우리는 전략 패턴이라 부르기로 했다.

전략패턴으로 상황을 해결 해보기

interface Transport {
    void delivery();
}

class Truck implements Transport {

    @Override
    public void delivery() {
        System.out.println("Truck Delivery!");
    }
}

class Ship implements Transport {

    @Override
    public void delivery() {
        System.out.println("Ship Delivery");
    }
}

class Client {
    private Transport transport;

    public void setTransport(Transport transport) {
        this.transport = transport;
    }

    public void deliveryTransport() {
        this.transport.delivery();
    }
}

public class DeliveryServiceStrategy {
    public static void main(String[] args) {
        Client client = new Client();
        client.setTransport(new Ship());
        
        client.deliveryTransport();
    }
}

위 코드로 ship을 추가 할때 코드 수정관련 문제를 해결 가능 할까…?

애초에 ship을 추가 할때 무슨 문제가 생겼던가?

결론적으론 ship 을 추가 할때 문제중 하나는 해결이 된것이다.

Ship을 사용하다 Truck 을 사용하고 싶을때! 의 문제

(설명을 덧붙히자면)

main에서 ship을 쓰다가 truck으로 service에 transport를 바꿔도 아래에 있는 client.deliveryTransport() 의 코드는 건들게 없다.

하지만 전략패턴은 행동을 추상화하는 행동 패턴이다.

팩토리 메서드에서는 객체를 생성하는 생성을 추상화하는 생성 패턴이다.

이 말은 즉 new Truck, new Ship 같은 코드의 역할을 추상화하는 패턴이라는 것이다.

이제 나의 생각은 그만 말하고 팩토리 메서드 패턴의 코드를 보자

팩토리 메서드 패턴의 코드

package DesignPattern.FactoryMethod.Factory;

// Transport 인터페이스, Truck 클래스, Ship 클래스

abstract class TransportCreator {
    public abstract Transport createTransport();

    public void deliveryTransport(){
        Transport transport = createTransport();
        transport.delivery();
    }
}

class TruckFactory extends TransportCreator {

    @Override
    public Transport createTransport() {
        return new Truck();
    }
}

class ShipFactory extends TransportCreator {

    @Override
    public Transport createTransport() {
        return new Ship();
    }
}

public class DeliveryService {
    public static void main(String[] args) {
        TransportCreator truckFactory = new TruckFactory();
        truckFactory.deliveryTransport();

        TransportCreator shipFactory = new ShipFactory();
        shipFactory.deliveryTransport();
    }
}

위코드를 얼핏 보면 그저 생성자 위치를 바꾸기만 한게 아닌가 싶다

결국 Truck을 사용하려면 TruckFactory 를 클라이언트 측에서 사용해야하고

Ship을 사용하려면 ShipFactory를 사용해야 하니 코드를 더 부풀려 적은게 아닌가 싶은 생각도 든다.

하지만 위 코드는 Truck에서 Ship으로 사용을 바꾸는게 아닌, Truck에서 Truck을 상속한 RedTruck, FiveTonTruck 등으로 변경핼야 할때 진가를 발휘한다
( 갑자기 모든 트럭을 빨간 트럭으로 바꿔야함! )

(설명을 덧붙히자면)

동적으로 바뀌는 Truck, Ship 에 대해서는 행동패턴인 전략패턴을 사용

정적으로 바뀌는 Truck, RedTruck, FiveTonTruck 에 대해서는 팩토리 메서드 패턴을 사용하면 된다.

RedTruck은 Truck을 사속한 확장된 클래스이다.

만약 이전 코드(인터페이스를 이용한) 에서 Truck에서 RedTruck으로 바꿔야 하는 일이 생기면 어떻게 될까

public class DeliveryServiceInterface {
    public static void main(String[] args) {
        Transport transport = new RedTruck();
        transport.delivery();
    }
}

정말 귀찮게도 클라이언트 측 코드를 손봐야 하는 일이 생겨버린다.

클라이언트는 트럭이 빨간색인지, 검은색인지 궁금하지않고 그저 배달만 되면 되는 것인데…

하지만 팩토리 메서드 패턴에서는?

class TruckFactory extends TransportCreator {

    @Override
    public Transport createTransport() {
        return new RedTruck();
    }
}

위처럼 create 부분만 변경하면 클라이언트는 그저 truckFactory.deliveryTransport(); 코드를 이용해 이전 코드를 계속 사용하면 된다.

참고 문헌
https://refactoring.guru/ko/design-patterns/factory-method

profile
소통과 기록이 무기(Weapon)인 개발자

0개의 댓글