Language/Design Pattern

[Design Pattern] 생성패턴 - Factory Method 패턴

JB1104 2023. 10. 16. 12:50
728x90
반응형
SMALL

팩토리 메소드(Factory Method) 패턴

  • 구체적으로 어떤 인스턴스를 만들지를 서브클래스가 결정하는 패턴
  • 상위 클래스는 객체 생성을 위한 인터페이스를 제공, 하위 클래스에게 구현체 클래스의 객체 생성일 위임하는 패턴
  • 팩토리 메소드 패턴은, 기능 혹은 역할에 따라 다양한 구현체(Product)가 존재하고, 그중에서 특정한 구현체를 만들 수 있는 다양한 팩토리(Creator)를 제공할 수 있다.

 

팩토리 메소드 (Factory Method) 사용 이유

  • 생성할 객체 타입을 예측할 수 없을 때
  • 생성할 객체를 기술하는 책임을 서브 클래스에 정의하고자 할 때
  • 객체 생성의 책임을 서브 클래스에 위임시키고 서브클래스에 대한 정보를 은닉하고자 할 때

 

장점

  • 기존 코드(인스턴스를 만드는 과정)를 수정하지 않고 새로운 인스턴스를 다른 방법으로 생성하도록 확장 가능
  • Product와 Creator 간의 결합도(Coupling)이 느슨함
  •  확장에는 열려있고 변경에는 닫혀있는 객체지향 원칙을 적용했기에 가능
    ★ 확장 : 새로운 인스턴스를 추가
    ★ 변경 : 기존 코드를 수정
  • 코드가 간결해짐
  • 병렬적 클래스 계층도를 연결하는 역할을 할 수 있음

단점

  • 클래스가 많아진다. 
  • 클래스 계층도 커질 수 있다.
    - 제품 클래스가 바뀔 때마다 새로운 서브 클래스를 생성해야 한다.
  • 클라이언트가 Creator 클래스를 반드시 상속해 Product를 생성해야 한다.

 

팩토리 메소드 (Factory Method) 구현방법

  • 팩토리 메소드의 목적은 "변경에는 닫혀있고, 확장에는 열려있는 구조"를 만드는 것이다.
    OCP (Open Closed Principle) 원칙을 지키기 위한 패턴에 가깝다.

 

 좋지 않은 코드 예시

@Setter
@ToString
public class Ship {

    private String name;
    private String email;
    private String color;

}

 

public class ShipFactory {
    public static Ship orderShip(final String name, final String email) {

        if(Objects.isNull(name) || name.isEmpty()) {
            throw new IllegalArgumentException("배 이름을 지어주세요");
        }
        if(Objects.isNull(name) || email.isEmpty()) {
            throw new IllegalArgumentException("연락처를 남겨주세요");
        }

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

        if(name.equalsIgnoreCase("whiteship")) {
            ship.setColor("white");
        }
        if(name.equalsIgnoreCase("blackship")) {
            ship.setColor("black");
        }

        return ship;
    }
}

 

public class orderShipTest {

    @Test
    void orderShipTest() {
        Ship whiteShip = ShipFactory.orderShip("WhiteShip", "abc@email.com");
        System.out.println(whiteShip);

        Ship blackShip = ShipFactory.orderShip("BlackShip", "def@email.com");
        System.out.println(blackShip);
    }
}

 

해당 코드의 문제점

Factory를 통해 요구조건에 따라 다른 Ship 객체가 반환되지만, 만약 요구사항이 달라진다면 Factory 코드도 변경되어야 한다. 즉, 변경에 닫혀있지 않은 코드이다.

 

좋은 코드 예시

  • 기존 ShipFactory에서 진행하던 validate도 ShipFactory로 하며, 배를 직접적으로 생성시키는 createShip() 메소드만 추상 메소드로 정의
public interface ShipFactoryInterface {
    default Ship orderShip(String name, String email) {
        validate(name, email);
        return createShip(name, email);
    }

    Ship createShip(String name, String email);

    private void validate(String name, String email) {
        if(Objects.isNull(name) || name.isEmpty()) {
            throw new IllegalArgumentException("배 이름을 지어주세요");
        }

        if(Objects.isNull(email) || email.isEmpty()) {
            throw new IllegalArgumentException("연락처를 남겨주세요");
        }
    }
}

 

 

  • 직접 배를 만들어 반환하는 createShip() 메서드를 구현하는 구현체를 각각 책임에 맞게 정의
public class WhiteShipFactory implements ShipFactoryInterface{
    @Override
    public Ship createShip(String name, String email) {
        return new WhiteShip(name, email);
    }
}

-----------------------------------------------------------

public class WhiteShip extends Ship{
    public WhiteShip(String name, String email) {
        setName(name);
        setColor("White");
        setEmail(email);
    }
}
public class BlackShipFactory implements ShipFactoryInterface{
    @Override
    public Ship createShip(String name, String email) {
        return new BlackShip(name, email);
    }
}
--------------------------------------------------------------

public class BlackShip extends Ship{
    public BlackShip(String name, String email) {
        setName(name);
        setColor("Black");
        setEmail(email);
    }
}

 

 

  • 생성할 배에 따라서, 해당하는 구현체를 선택
    @Test
    void orderShipInterfaceTest() {
        Ship whiteShip = new WhiteShipFactory().orderShip("WhiteShip1", "abc@email.cmo");
        System.out.println(whiteShip);

        Ship blackShip = new BlackShipFactory().orderShip("BlackShip1", "def@email.cmo");
        System.out.println(blackShip);
    }

 

중요
WhiteShip, BlackShip 등 요구사항 변경에 의해 추가적인 배 객체를 만들어야 해도,
기존 코드를 수정하는 것이 아니라,
createShip() 메소드를 구현하는 구현체만 추가해 주면 된다.

즉, 변경에는 닫혀있고 확장에는 열려있는 OCP를 지키는 구조가 된다.

 

※ Java8 default Method에 대해서 잘 모른다면, 아래 포스팅 참조

2023.10.16 - [Language/Java] - [Java] Java8 default Method 개념 정리


 

팩토리 메소드 패턴 복습

장점

  • 요구사항의 변경에도 기존코드를 변경하지 않고 확장하여 처리할 수 있는 구조가 된다.
  • Product와 Creator 간 관계를 느슨한 결합으로 묶었기 때문에 가능한 것이다.

단점

  • 각자의 역할을 가지고 있는 클래스가 늘어남은 막을 수 없다.
    즉, 관리포인트가 증가하게 된다.

OCP (Open Closed Principle)

  • 변경에 닫혀있다 : 기존 코드를 변경하지 않는다
  • 확장에 열려있다 : 새로운 코드를 확장한다

default 메소드

  • Java8에 추가됨
  • Java8 이전에는 interface는 추상 메소드만 선언 가능했었지만, 이후로는 default 메소드를 정의할 수 있기 때문에
    추상 메소드를 상속받는 모든 구현체에서 필요한 기능을 상속받을 수 있도록 되었다.

 

 

728x90
반응형
LIST