[Design Pattern] Factory Method

Roy·2024년 1월 19일

Design Pattern

목록 보기
2/5

개요

디자인 패턴의 생성 패턴 중 하나인 Factory Method Pattern이다.

다양한 구현체(Product)들이 있는 상황에서 특정한 구현체(Product)를 만들 수 있는 다양한 팩토리(Creator)를 제공하는 패턴이다.

자동차를 예로 들어 본다면, 자동차가 구현체(Product)에 해당하며 자동차를 만드는 클래스가 팩토리(Creator)에 해당한다.

자동차에는 이름, 브랜드, 색상이 있다. 브랜드마다 대표하는 자동차들이 있다.
Lamborghini에는 Aventador, Ferrari에는 F8 Spider, Bugatti에는 Veyron이 있다.
Aventador는 하얀색, F8 Spider는 노란색, Veyron은 검은색으로 제작된다고 가정하자.

자동차의 이름에 따라 자동차를 만들어야 하는 공장이 달라져야 한다.
이를 코드로 구현하려면, if else 조건문이 무조건 들어가게 된다.

우선 Aventador와 F8 Spider를 만드는 코드를 작성해보자.

수정 전 코드

Car.java

package vanillafleet.factorymethod.before;

public class Car {

    private String name;
    private String color;
    private String brand;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    @Override
    public String toString() {
        return "Car {" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", brand ='" + brand + '\'' +
                '}';

    }

}

CarFactory.java

package vanillafleet.factorymethod.before;

public class CarFactory {

    public static Car orderCar(String name, String email) {

        // validate input
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Make name for car.");
        }
        if (email == null || email.isBlank()) {
            throw new IllegalArgumentException("Please leave us your contact info.");
        }

        prepareFor(name);

        Car car = new Car();
        car.setName(name);

        // Logo of ship differs from its name
        if (name.equalsIgnoreCase("Aventador")) {
            car.setBrand("Lamborghini");
        } else if (name.equalsIgnoreCase("F8 Spider")){
            car.setBrand("Ferrari");
        }

        // set color
        if (name.equalsIgnoreCase("Aventador")) {
            car.setColor("white");
        } else if (name.equalsIgnoreCase("F8 Spider")) {
            car.setColor("yellow");
        }

        sendEmailTo(email, car);

        return car;
    }

    private static void prepareFor(String name) {
        System.out.println(name + " is ready to be crafted.");
    }

    private static void sendEmailTo(String email, Car car) {
        System.out.println(car.getName() + " is crafted.");
    }

}

Client.java

package vanillafleet.factorymethod.before;

public class Client {

    public static void main(String[] args) {
        Car Aventador = CarFactory.orderCar("Aventador", "client@gmail.com");
        System.out.println(Aventador);

        Car F8_Spider = CarFactory.orderCar("F8 Spider", "client@gmail.com");
        System.out.println(F8_Spider);
    }

}

# output
Aventador is ready to be crafted.
Aventador is crafted.
Car {name='Aventador', color='white', brand ='Lamborghini'}
F8 Spider is ready to be crafted.
F8 Spider is crafted.
Car {name='F8 Spider', color='yellow', brand ='Ferrari'}

문제점

우선, Car.java와 CarFactory.java에서 OCP 원칙이 지켜지지 않았다.
OCP 원칙은 코드가 확장에는 열려있고, 수정에는 닫혀있어야 한다고 말한다.
추가로 요청사항이 들어오거나(Bugatti의 Veyron를 구현), 자동차를 주문하는 프로세스에 변화가 있는 경우,
CarFactory의 orderCar를 수정해야 하므로, OCP를 준수하지 못한다.

둘째, CarFactory의 orderCar 메소드의 가독성이 좋아보이지 않는다.
나는 현업에서 이것보다 최소 20배 이상 복잡하게 꼬여있는 코드를 접하고 관리했었다.
요구사항을 반영하는 것만 생각하고 코드를 작성하면, 아주 쉽게 스파게티 코드가 된다.
스파게티 같이 꼬인 코드는 이해하기 어려운 뿐 아니라 유지보수가 엄청 까다롭다.

Factory Method Pattern 적용

여기에 Factory Method 패턴을 도입해보자.
아래 그림은 Factory Method의 UML이다.

  • 우리는 Car를 상속하는 Aventador Class, F8 Spider Class를 만들어, 각각의 요구사항을 클래스 내부에 구현할 것이다.
  • CarFactory Class를 Interface로 바꾸고, Aventador와 F8 Spider를 만드는 팩토리를 구현할 것이다. (AventadorFactory, F8SpiderFactory)

이를 적용하여 UML로 다시 그려보면, 아래와 같은 그림이 나온다.

Factory Pattern이 적용된 코드

Car.java

package vanillafleet.factorymethod.after;

public class Car {

    private String name;
    private String color;
    private String brand;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    @Override
    public String toString() {
        return "Car {" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", brand ='" + brand + '\'' +
                '}';

    }

}

Aventador.java

package vanillafleet.factorymethod.after;

public class Aventador extends Car {
    public Aventador() {
        setName("Aventador");
        setColor("white");
        setBrand("Lamborghini");
    }
}

F8Spider.java

package vanillafleet.factorymethod.after;

public class F8Spider extends Car {
    public F8Spider() {
        setName("F8 Spider");
        setColor("yellow");
        setBrand("Ferrari");
    }
}

CarFactory.java

package vanillafleet.factorymethod.after;

public interface CarFactory {

    default Car orderCar(String name, String email) {
        validate(name, email);
        prepareFor(name);
        Car car = createCar();
        sendEmailTo(email, car);
        return car;
    }

    private void sendEmailTo(String email, Car car) {
        System.out.println(car.getName() + " is crafted.");
    }

    Car createCar();

    private void prepareFor(String name) {
        System.out.println(name + " is ready to be crafted.");
    }

    private void validate(String name, String email) {
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Make name for car.");
        }
        if (email == null || email.isBlank()) {
            throw new IllegalArgumentException("Please leave us your email.");
        }
    }

}

AventadorFactory.java

package vanillafleet.factorymethod.after;

public class AventadorFactory implements CarFactory {

    @Override
    public Car createCar() {
        return new Aventador();
    }
}

F8SpiderFactory.java

package vanillafleet.factorymethod.after;

public class F8SpiderFactory implements CarFactory{
    @Override
    public Car createCar() {
        return new F8Spider();
    }
}

Client.java

package vanillafleet.factorymethod.after;

public class Client {

    public static void main(String[] args) {
        Client client = new Client();
        Car Aventador = new AventadorFactory().orderCar("Aventador", "client@gmail.com");
        System.out.println(Aventador);

        Car F8_Spider = new F8SpiderFactory().orderCar("F8 Spider", "client@gmail.com");
        System.out.println(F8_Spider);
    }

}

client 코드가 아주 깔끔하게 정리되었다.

profile
Backend Engineer

0개의 댓글