추상 팩토리 패턴을 이해한다.

Goal

  • 추상 팩토리 패턴의 개념을 이해한다.
  • 예시를 통해 추상 팩토리 패턴을 이해한다.

추상 팩토리 패턴이란

참고

예시

엘리베이터 부품 업체 변경하기

템플릿 메서드 패턴 을 적용한 예제 코드

public abstract class Door {
  private DoorStatus doorStatus;

  public Door() { doorStatus = DoorStatus.CLOSED; }
  public DoorStatus getDoorStatus() { return doorStatus; }

  // primitive 또는 hook 메서드
  protected abstract void doClose();
  // 템플릿 메서드: 문을 닫는 기능 수행
  public void close() {
    // 이미 문이 닫혀 있으면 아무 동작을 하지 않음
    if(doorStatus == DoorStatus.CLOSED)
      return;
    // primitive 또는 hook 메서드. 하위 클래스에서 오버라이드
    doClose(); // 실제로 문을 닫는 동작을 수행
    doorStatus = DoorStatus.CLOSED; // 문의 상태를 닫힘으로 기록
  }

  // primitive 또는 hook 메서드
  protected abstract void doOpen();
  // 템플릿 메서드: 문을 여는 기능 수행
  public void open() {
    // 이미 문이 열려 있으면 아무 동작을 하지 않음
    if(doorStatus == DoorStatus.OPENED)
      return;
    // primitive 또는 hook 메서드. 하위 클래스에서 오버라이드
    doOpen(); // 실제로 문을 여는 동작을 수행
    doorStatus = DoorStatus.OPENED; // 문의 상태를 열림으로 기록
  }
}
public class LGDoor extends Door {
  protected void doClose() { System.out.println("close LG Door"); }
  protected void doOpen() { System.out.println("open LG Door"); }
}
public class HyundaiDoor extends Door {
  protected void doClose() { System.out.println("close Hyundai Door"); }
  protected void doOpen() { System.out.println("open Hyundai Door"); }
}

팩토리 메서드 패턴 을 적용한 예제 코드

public enm VendorID { LG, HYUNDAI }
public class MotorFactory {
  // vendorID에 따라 LGMotor 또는 HyundaiMotor 객체를 생성함
  public static Motor createMotor(VendorID vendorID) {
    Motor motor = null;

    switch (vendorID) {
      case LG:
        motor = new LGMotor();
        break;
      case HYUNDAI:
        motor = new HyundaiMotor();
        break;
    }
    return motor;
  }
}
public class DoorFactory {
  // vendorID에 따라 LGDoor 또는 HyundaiDoor 객체를 생성함
  public static Door createDoor(VendorID vendorID) {
    Door door = null;

    switch (vendorID) {
      case LG:
        door = new LGDoor();
        break;
      case HYUNDAI:
        door = new HyundaiDoor();
        break;
    }
    return door;
  }
}
public class Client {
  public static void main(String[] args) {
    Door lgDoor = DoorFactory.createDoor(VendorID.LG); // 팩토리 메서드 호출
    Motor lgMotor = MotorFactory.createMotor(VendorID.LG); // 팩토리 메서드 호출
    lgMotor.setDoor(lgDoor);

    lgDoor.open();
    lgMotor.move(Direction.UP);
  }
}

문제점

  1. 다른 제조 업체의 부품을 사용해야 하는 경우
    • LG의 부품 대신 현대의 부품(HyundaiMotor, HyundaiDoor 클래스)을 사용해야 한다면?
      public class Client {
        public static void main(String[] args) {
       Door hyundaiDoor = DoorFactory.createDoor(VendorID.HYUNDAI); // 팩토리 메서드 호출
       Motor hyundaiMotor = MotorFactory.createMotor(VendorID.HYUNDAI); // 팩토리 메서드 호출
       hyundaiMotor.setDoor(lgDoor);
       // 문을 먼저 연다.
       hyundaiDoor.open();
       hyundaiMotor.move(Direction.UP);
        }
      }
      
    • 총 10개의 부품을 사용해야 한다면 각 Factory 클래스를 구현하고 이들의 Factory 객체를 각각 생성해야 한다.
    • 즉, 부품의 수가 많아지면 특정 업체별 부품을 생성하는 코드의 길이가 길어지고 복잡해진다.
  2. 새로운 제조 업체의 부품을 지원해야 하는 경우
    • 삼성의 부품(SamsungMotor, SamsungDoor 클래스)을 지원해야 한다면?
      public class DoorFactory {
        // vendorID에 따라 LGDoor 또는 HyundaiDoor 객체를 생성함
        public static Door createDoor(VendorID vendorID) {
       Door door = null;
       // 삼성의 부품을 추가
       switch (vendorID) {
         case LG:
       door = new LGDoor();
       break;
         case HYUNDAI:
       door = new HyundaiDoor();
       break;
         case SAMSUNG:
       door = new SamsungDoor();
       break;
       }
       return door;
        }
      }
      
    • DoorFactory 클래스뿐만 아니라 나머지 9개의 부품과 연관된 Factory 클래스에서도 마찬가지로 삼성의 부품을 생성하도록 변경해야 한다.
    • 또한, 위와 마찬가지로 특정 업체별 부품을 생성하는 코드에서 삼성의 부품을 생성하도록 모두 변경해야 한다.

해결책

여러 종류의 객체를 생성할 때 객체들 사이의 관련성이 있는 경우 라면 각 종류별로 별도의 Factory 클래스를 사용하는 대신 관련 객체들을 일관성 있게 생성하는 Factory 클래스를 사용 하는 것이 편리할 수 있다.

/* 추상 부품을 생성하는 추상 팩토리 클래스 */
public abstract class ElevatorFactory {
  public abstract Motor createMotor();
  public abstract Door createDoor();
}
/* LG 부품을 생성하는 팩토리 클래스 */
public class LGElevatorFactory extends ElevatorFactory {
  public Motor createMotor() { return new LGMotor(); }
  public Door createDoor() { return new LGDoor(); }
}
/* Hyundai 부품을 생성하는 팩토리 클래스 */
public class HyundaiElevatorFactory extends ElevatorFactory {
  public Motor createMotor() { return new HyundaiMotor(); }
  public Door createDoor() { return new HyundaiDoor(); }
}
/* 주어진 업체의 이름에 따라 부품을 생성하는 Client 클래스 */
public class Client {
  public static void main(String[] args) {
    ElevatorFactory factory = null;
    String vendorName = args[0];

    // 인자에 따라 LG 또는 Hyundai 팩토리를 생성
    if(vendorName.equalsIgnoreCase("LG"))
      factory = new LGElevatorFactory();
    else
      factory = new HyundaiElevatorFactory();

    Door door = factory.createDoor(); // 해당 업체의 Door 생성
    Motor motor = factory.createMotor(); // 해당 업체의 Motor 생성
    motor.setDoor(door);

    door.open();
    motor.move(Direction.UP);
  }
}
/* Samsung 부품을 생성하는 팩토리 클래스 */
public class SamsungElevatorFactory extends ElevatorFactory {
  public Motor createMotor() { return new SamsungMotor(); }
  public Door createDoor() { return new SamsungDoor(); }
}
/* Samsung Door 클래스 */
public class SamsungDoor extends Door {
  protected void doClose() { System.out.println("close Samsung Door"); }
  protected void doOpen() { System.out.println("open Samsung Door"); }
}
/* Samsung Motor 클래스 */
public class SamsungDoor extends Door {
  protected void doClose() { System.out.println("close Samsung Motor"); }
  protected void doOpen() { System.out.println("open Samsung Motor"); }
}
/* 주어진 업체의 이름에 따라 부품을 생성하는 Client 클래스 */
public class Client {
  public static void main(String[] args) {
    ElevatorFactory factory = null;
    String vendorName = args[0];

    // 인자에 따라 LG 또는 Samsung 또는 Hyundai 팩토리를 생성
    if(vendorName.equalsIgnoreCase("LG"))
      factory = new LGElevatorFactory();
    else if(vendorName.equalsIgnoreCase("Samsung")) // 추가
      factory = new SamsungElevatorFactory();
    else
      factory = new HyundaiElevatorFactory();

    동일
  }
}

추가 보완 해결책(정확한 추상 팩토리 패턴 적용)

과정 1
“팩토리 메서드 패턴”: 제조 업체별 Factory 객체를 생성하는 방식을 캡슐화한다.

/* 팩토리 클래스에 팩토리 메서드 패턴을 적용 */
public class ElevatorFactoryFactory {
  public static ElevatorFactory getFectory(VendorID vendorID) {
    ElevatorFactory factory = null;

    switch (vendorID) {
      case LG: // LG 팩토리 생성
        factory = new LGElevatorFactory.getInstance();
        break;
      case HYUNDAI: // Hyundai 팩토리 생성
        factory = new HyundaiElevatorFactory.getInstance();
        break;
      case SAMSUNG: // Samsung 팩토리 생성
        factory = new SamsungElevatorFactory.getInstance();
        break;
    }
    return factory;
  }
}

과정 2
“싱글턴 패턴”: 제조 업체별 Factory 객체는 각각 1개만 있으면 된다.

/* 싱글턴 패턴을 적용한 LG 팩토리 */
public class LGElevatorFactory extends ElevatorFactory {
  public static ElevatorFactory getFectory(VendorID vendorID) {
    private static ElevatorFactory factory;
    private LGElevatorFactory() { } // 생성자를 private로

    public static ElevatorFactory getInstance() {
      if(factory == null)
        factory = new LGElevatorFactory();

      return factory;
    }

    public Motor createMotor() { return new SamsungMotor(); }
    public Door createDoor() { return new SamsungDoor(); }
  }
}
/* 싱글턴 패턴을 적용한 Hyundai 팩토리 */
동일한 방식
/* 싱글턴 패턴을 적용한 Samsung 팩토리 */
동일한 방식

추상 팩토리 패턴을 이용한 방법을 사용하는 Client

/* 주어진 업체의 이름에 따라 부품을 생성하는 Client 클래스 */
public class Client {
  public static void main(String[] args) {
    String vendorName = args[0];
    VendorID vendorID;

    // 인자에 따라 LG 또는 Samsung 또는 Hyundai 팩토리를 생성
    if(vendorName.equalsIgnoreCase("LG"))
      vendorID = VendorID.LG;
    else if(vendorName.equalsIgnoreCase("Samsung"))
      vendorID = VendorID.SAMSUNG;
    else
      vendorID = VendorID.HYUNDAI;

    ElevatorFactory factory = ElevatorFactoryFactory.getFactory(vendorID);

    동일
  }
}

추상 팩토리 패턴 최종 클래스 다이어그램

관련된 Post

References