[객체 생성 패턴] Chapter 2-3. Factory Method Pattern : 인터페이스 적용하기

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

✍️ 팩토리 메서드 패턴, 인터페이스 적용하기

팩토리 메서드 패턴을 사용하더라도 새로운 팩토리가 생기면 결국 클라이언트 코드에 변화가 생기는 문제 아닌 문제점을 확인했다. 그러나 클라이언트는 어떤 팩토리를 가지고 제품을 만들지 알아야 하기 때문에 클라이언트 코드에 변화가 생기는 것은 당연하다. 집중해야 하는 부분은 제품과 팩토리를 확장할 때 기존 구조가 변경되지 않는 점이다. 

 

단, 클라이언트 코드가 인터페이스 기반으로 작성됐다면 클라이언트 코드에 변화를 최소화할 순 있다.

 

변경 전

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

        Ship rabbitShip = new RabbitshipFactory().orderShip("rabbitship", "kangworld@email.com");
        System.out.println(rabbitShip);
    }
}

 

구체적인 팩토리 인스턴스를 만들어서 print 메서드에 주입하면 메서드는 인터페이스 기반으로 작성되었기 때문에 변경되지 않는다.

변경 후

public class Client {
    public static void main(String[] args) {
        Client client = new Client();

        client.print(new TurtleshipFactory(), "turtleship", "kangworld@email.com");
        client.print(new RabbitshipFactory(), "rabbitship", "kangworld@email.com");
    }

    public void print(ShipFactory shipFactory, String name, String email){
        Ship ship = shipFactory.orderShip(name, email);
        System.out.println(ship);
    }
}

 

🍊 팩토리 메서드 패턴, Java8로 구현

Java11은 인터페이스에 private 메서드를 사용할 수 있지만, 현재 가장 많이 사용되는 Java8은 인터페이스에 private 메서드 사용이 불가능하다. 따라서 추상 클래스로 다시 구현해 보려 한다. 

 

ShipFactory 인터페이스에 private 메서드를 모두 추상 메서드로 변경한다.

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;
    }

    void validate(String name, String email);

    void prepareFor(String name);

    void sendEmailTo(String email, Ship ship);
}

 

DefaultShipFactory에서 ShipFactory 인터페이스를 구현한다. 쉽게 생각하면 기존 ShipFactory 인터페이스와 구현체 사이에 추상 클래스라는 레이어가 하나 추가됐을 뿐이다.  

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

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

    public void sendEmailTo(String email, Ship ship) {
        System.out.println(ship.name + " 다 만들었습니다.");
    }
}
public class TurtleshipFactory extends DefaultShipFactory{
    @Override
    public Ship createShip() {
        return new Turtleship();
    }
}

 

 

제품(Ship)도 마찬가지다 Ship을 인터페이스로 변경하고 중간에 추상 클래스를 두거나 바로 구현체를 둬서 구조를 변경, 확장할 수 있다.

 

🐢 정리

정리하면 제품군에도 계층구조가 있고 팩토리에도 계층구조가 있어서 구체적인 팩토리 안에서 구체적인 제품을 만들어낸다는 것이다.

 

 

 

인프런의 백기선님의 강의 코딩으로 학습하는 GoF의 디자인 패턴을 참고해서 작성했습니다.

 

코딩으로 학습하는 GoF의 디자인 패턴 - 인프런 | 강의

디자인 패턴을 알고 있다면 스프링 뿐 아니라 여러 다양한 기술 및 프로그래밍 언어도 보다 쉽게 학습할 수 있습니다. 또한, 보다 유연하고 재사용성이 뛰어난 객체 지향 소프트웨어를 개발할

www.inflearn.com