팩토리 메서드 패턴 (Factory Method Pattern)

반응형
728x90
반응형

 

수강완료한 강의 복습해보자
(코딩으로 학습하는 GoF의 디자인 패턴)

 

 

팩토리 메서드 (Factory Method)

  • 구체적으로 어떤 인스턴스를 만들지는 서브 클래스가 정한다.
  • 다양한 구현체(Product)가 있고, 그중에서 특정한 구현체를 만들 수 있는 다양한 팩토리(Creator)를 제공할 수 있다

 

 

적용 전 코드 (Before)

Ship.java
@Getter
@Setter
@ToString
public class Ship {
    private String name;
    private String color;
    private String logo;
}

 

Client.java
public class Client {
    public static void main(String[] args) {
        Ship whiteship = ShipFactory.orderShip("Whiteship", "keesun@mail.com");
        System.out.println(whiteship);

        Ship blackship = ShipFactory.orderShip("Blackship", "keesun@mail.com");
        System.out.println(blackship);
    }
}

 

ShipFactory.java
public class ShipFactory {
    public static Ship orderShip(String name, String email) {
        // validate
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("배 이름을 지어주세요.");
        }
        if (email == null || email.isBlank()) {
            throw new IllegalArgumentException("연락처를 남겨주세요.");
        }

        prepareFor(name);

        Ship ship = new Ship();
        ship.setName(name);

        // Customizing for specific name
        if (name.equalsIgnoreCase("whiteship")) {
            ship.setLogo("\\uD83D\\uDEE5️");
        } else if (name.equalsIgnoreCase("blackship")) {
            ship.setLogo("⚓");
        }

        // coloring
        if (name.equalsIgnoreCase("whiteship")) {
            ship.setColor("whiteship");
        } else if (name.equalsIgnoreCase("blackship")) {
            ship.setColor("black");
        }

        // notify
        sendEmailTo(email, ship);

        return ship;
    }

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

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

}

 

1) 요구사항

Whiteship, BlackShip 2개의 Ship을 만들고 싶다.

 

2) 문제점

ShipFactory.orderShip() 메서드에 모든 비즈니스 로직이 포함되어있다.

 

3) 추가요건

name 에 whiteShip, blackShip 외의 redShip을 추가해주세요.

name 값에 따라 분기되는 로직 처리가 너무 많다. 수정되는 코드가 무수히 많아, 놓쳐서 버그가 발생할 가능성이 크다.

 

4) 추가요건

배의 특성에 logo, color 외의 size를 추가해주세요.

name 값에 따라 설정될 size를 모든 코드를 분석하여 추가해줘야한다.

 

 

 

적용 후 코드 (After)

 

Ship.java
@Getter
@Setter
@ToString
public class Ship {
    private String name;
    private String color;
    private String logo;
}

 

Blackship.java
public class Blackship extends Ship {
    public Blackship() {
        setName("blackship");
        setLogo("⚓");
        setColor("black");
    }
}

 

Whiteship.java
public class Whiteship extends Ship {
    public Whiteship() {
        setName("whiteship");
        setLogo("\\uD83D\\uDEE5");
        setColor("white");
    }
}

 

ShipFactory.java
public interface ShipFactory {
    // orderShip
    default Ship orderShip(String name, String email) {
        // 어떤일이 일어나는지 default 로 정의해보자.
        validate(name, email);

        prepareFor(name);

        // 하위클래스에 위임
        Ship ship = createShip();

        // notify
        sendEmailTo(email, ship);

        return ship;
    }

    /**
     * 추상메서드
     * 하위클래스가 정의해야하는 메서드
     * @return
     */
    Ship createShip();

    // java9 부터 인터페이스에 private 메서드 사용 가능
    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.getName() + " 다 만들었습니다.");
    }
}

 

WhiteshipFactory.java
public class WhiteshipFactory implements ShipFactory {
    @Override
    public Ship createShip() {
        return new Whiteship();
    }
}

 

BlackshipFactory.java
public class BlackshipFactory implements ShipFactory {
    @Override
    public Ship createShip() {
        return new Blackship();
    }
}

 

 

핵심 로직 비교

Before

...
Ship ship = new Ship();
ship.setName(name);

// Customizing for specific name
if (name.equalsIgnoreCase("whiteship")) {
    ship.setLogo("\\uD83D\\uDEE5️");
} else if (name.equalsIgnoreCase("blackship")) {
    ship.setLogo("⚓");
}

// coloring
if (name.equalsIgnoreCase("whiteship")) {
    ship.setColor("whiteship");
} else if (name.equalsIgnoreCase("blackship")) {
    ship.setColor("black");
}
...

 

After

...
default Ship orderShip(String name, String email) {
    // 어떤일이 일어나는지 default 로 정의해보자.
    validate(name, email);
    
    prepareFor(name);
    
    // 하위클래스에 위임
    Ship ship = createShip();
    
    // notify
    sendEmailTo(email, ship);
    
    return ship;
}
...

public class Blackship extends Ship {
    public Blackship() {
        setName("blackship");
        setLogo("⚓");
        setColor("black");
    }
}
...
public class Whiteship extends Ship {
    public Whiteship() {
        setName("whiteship");
        setLogo("\\uD83D\\uDEE5");
        setColor("white");
    }
}
...

적용 전 코드에서 if 문을 사용하여 name 에 따라 처리하던 코드를 인터페이스의 추상메서드를 활용하여 하위 클래스에 위임했다.

 

 

Client

public class Client {
    public static void main(String[] args) {
        Client client = new Client();
        client.print(new WhiteshipFactory(), "whiteship", "keesun@mail.com");
        client.print(new BlackshipFactory(), "blackship", "keesun@mail.com");
    }

    private void print(ShipFactory shipFactory, String name, String email) {
        System.out.println(shipFactory.orderShip(name, email));
    }
}

Client 에서 원하는 Ship의 팩토리를 생성하여 유연하게 처리할 수 있게되었다.

 

 

 

위 Before 에서 봤던 추가 요건을 다시 생각해보자.

1) 추가요건

[추가요건]
name 에 whiteShip, blackShip 외의 redShip을 추가해주세요.

RedShipFactory.java 파일을 생성함으로써 다른 비즈니스 로직 수정 없이 유연하게 추가가 가능하다.

public class Redship extends Ship {
    public Redship() {
        setName("redShip");
        setLogo("⚓");
        setColor("red");
    }
}

 

2) 추가요건

[추가요건]
배의 특성에 logo, color 외의 size를 추가해주세요.

각 White, Black, Red Factory 파일 안에 설정하여 Client 코드의 수정 없이 설정할 수 있다.

 

 

 

적용 결과

 

이로써, 수정에 유연해지고, Client 와 구현 클래스들의 결합도가 낮아졌다. 팩토리 메서드 패턴을 적용함으로써 위 코드는 아래와 같은 장점을 얻었다.

 

 

장점
  1. 기존 코드를 건드리지 않고 새로운 인스턴스를 다른 방법으로 얼마든지 확장이 가능하다.
  2. 결합도를 느슨하게 설계할 수 있다.
  3. 확장에 열려있고, 변경에 닫혀있는 객체지향 원칙을 따를 수 있다.
  4. 자바 8에 추가된 default 메서드를 활용하여 인터페이스의 기본적인 구현체를 만들 수 있다.
  5. 자바 9에 추가된 private 메서드 기능도 활용할 수 있다. 

 

단점
  1. 여러 Ship이 생길때마다 클래스가 늘어나는 단점이 있다.

→ 위 추가 요건에서 RedShip이 추가되었을때 RedShipFacotry 파일이 추가되었다.

 

 

 

 

github : https://github.com/seohaem/java/tree/master/java_GoFdesignpattern

강의 : https://www.inflearn.com/course/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4

 

 

반응형

Designed by JB FACTORY