[객체 생성 패턴] Chapter 2-2. Factory Method Pattern : 패턴 적용하기

[객체 생성 패턴] Chapter 2-1. Factory Method Pattern : 패턴 소개

✍️ 팩토리 메서드 패턴, 적용하기

먼저, ShipFactory 클래스를 인터페이스(혹은 추상 클래스)로 변경하고 기존에 orderShip 메서드에서 달라지는 부분(Ship 인스턴스를 만드는 부분)을 추상화 시킨다. 

 

ShipFactory가 인터페이스로 변경됐다. orderShip에 공통 코드는 살려두고 Ship 인스턴스를 만드는 작업은 구현체가 결정한다.

 

ShipFactory interface

public interface ShipFactory {
    Ship createShip();

    default Ship orderShip(String name, String email){
        validate(name, email);
        prepareFor(name);

        Ship ship = createShip();

        sendEmailTo(email, ship);

        return ship;
    }

    private void validate(String name, String email){
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("배 이름을 지어주세요");
        }
        if (email == null || email.isBlank()) {
            throw new IllegalArgumentException("이메일을 남겨주세요");
        }
    }

    private void prepareFor(String name) {
        System.out.println(name + " 만들 준비 중");
    }

    private void sendEmailTo(String email, Ship ship) {
        System.out.println(ship.name + " 다 만들었습니다.");
    }
}

여담으로 Java8부터 인터페이스 default 메서드가 추가됐고 Java9부터 인터페이스 private 메서드가 추가됐다.

C#으로 동일하게 구현하려면 추상 클래스를 사용해야 한다.

 

어떤 인스턴스를 만들진 인터페이스를 구현한 클래스에서 결정한다.

 

Turtleship 구현체

public class TurtleshipFactory implements ShipFactory {
    @Override
    public Ship createShip() {
        return new Turtleship();
    }
}

 

팩토리가 생산할 배의 기본이 될 클래스를 하나 정의하고, Turtleship이 이를 상속받도록 변경하면 기존에 클래스에 특화되어 분기문으로 작성됐던 코드를 각 클래스 별로 분리할 수 있다.

public class Ship {
    // 편의상 한정자를 public으로
    public String name;
    public String color;
    public String logo;

    @Override
    public String toString() {
        return "Ship{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                ", logo='" + logo + '\'' +
                '}';
    }
}
public class Turtleship extends Ship {
    public Turtleship() {
        name = "turtleship";
        logo = "\uD83D\uDC22";
        color = "green";
    }
}

 

수행 결과 

public class Client {
    public static void main(String[] args) {
        Ship turtleship = new TurtleshipFactory().orderShip("turtleship", "kangworld@email.com");
        System.out.println(turtleship.toString());
    }
}
turtleship 만들 준비 중
turtleship 다 만들었습니다.
Ship{name='turtleship', color='green', logo='🐢'}

 

🐇 새로운 Ship 추가해보기

이제 토끼선 Rabbitship과 이를 만드는 RabbitshipFactory를 추가했을 때  확장에 열려있고 수정엔 닫혀있는 구조로 변경됐을까? 기존에 작성했던 코드들이 수정되지 않을까? 

 

Rabbitship class

public class Rabbitship extends Ship {
    public Rabbitship(){
        name = "rabbitship";
        logo = "\uD83D\uDC07";
        color = "pink";
    }
}

 

RabbitshipFactory class

public class RabbitshipFactory implements ShipFactory{
    @Override
    public Ship createShip() {
        return new Rabbitship();
    }
}

 

놀랍게도 Rabbitship 클래스와 이를 만드는 팩토리 RabbitshipFactory 클래스를 추가했음에도 기존에 배를 생산하는 로직에 수정사항이 없다.

 

수행 결과 

public class Client {
    public static void main(String[] args) {
        Ship turtleship = new TurtleshipFactory().orderShip("turtleship", "kangworld@email.com");
        System.out.println(turtleship.toString());

        Ship rabbitShip = new RabbitshipFactory().orderShip("rabbitship", "kangworld@email.com");
        System.out.println(rabbitShip.toString());
    }
}
turtleship 만들 준비 중
turtleship 다 만들었습니다.
Ship{name='turtleship', color='green', logo='🐢'}
rabbitship 만들 준비 중
rabbitship 다 만들었습니다.
Ship{name='rabbitship', color='pink', logo='🐇'}

 

🐢 정리

물론 이런 의문도 들 수 있다.

??? : 이놈아 클라이언트 코드는 변경되지 않았느냐.

맞다.

??? : 그렇다면 이게 변경에 닫혀있는 게 맞느냐.

 

그래서 보통은 인터페이스 기반으로 코드를 작성하고 구체적인 클래스는 외부에서 의존성을 주입하는 방법을 사용하면 클라이언트 코드의 수정도 최소화할 수 있다.