팩토리 메서드 패턴을 이해한다.

Goal

  • 팩토리 메서드 패턴의 개념을 이해한다.
  • 예시를 통해 팩토리 메서드 패턴을 이해한다.

팩토리 메서드 패턴이란

참고

예시

여러 가지 방식의 엘리베이터 스케줄링 방법 지원하기

public class ElevatorManager {
  private List<ElevatorController> controllers;
  private ThroughputScheduler scheduler;

  // 주어진 수만큼의 ElevatorController를 생성함
  public ElevatorManager(int controllerCount) {
    // 엘리베이터의 이동을 책임지는 ElevatorController 객체 생성
    controllers = new ArrayList<ElevatorController>(controllerCount);
    for (int i=0; i<controllerCount; i++) {
      ElevatorController controller = new ElevatorController(1);
      controllers.add(controller);
    }
    // 엘리베이터를 스케줄링(엘리베이터 선택)하기 위한 ThroughputScheduler 객체 생성
    scheduler = new ThroughputScheduler();
  }
  // 요청에 따라 엘리베이터를 선택하고 이동시킴
  void requestElevator(int destination, Direction direction) {
    // ThroughputScheduler를 이용해 엘리베이터를 선택함
    int selectedElevator = scheduler.selectElevator(this, destination, direction);
    // 선택된 엘리베이터를 이동시킴
    controllers.get(selectElevator).gotoFloor(destination);
  }
}
public class ElevatorController {
  private int id; // 엘리베이터 ID
  private int curFloor; // 현재 층

  public ElevatorController(int id) {
    this.id = id;
    curFloor = 1;
  }
  public void gotoFloor(int destination) {
    System.out.print("Elevator [" + id + "] Floor: " + curFloor);

    // 현재 층 갱신, 즉 주어진 목적지 층(destination)으로 엘리베이터가 이동함
    curFloor = destination;
    System.out.println(" ==> " + curFloor);
  }
}
/* 엘리베이터 작업 처리량을 최대화시키는 전략의 클래스 */
public class ThroughputScheduler {
  public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
    return 0; // 임의로 선택함
  }
}

문제점 1

  1. 다른 스케줄링 전략 을 사용하는 경우
    • 엘리베이터 작업 처리량을 최대화(ThroughputScheduler 클래스)시키는 전략이 아닌 사용자의 대기 시간을 최소화하는 엘리베이터 선택 전략을 사용해야 한다면?
  2. 프로그램 실행 중에 스케줄링 전략을 변경, 즉 동적 스케줄링을 지원 해야하는 경우
    • 오전에는 대기 시간 최소화 전략을 사용하고, 오후에는 처리량 최대화 전략을 사용해야 한다면?

문제점 2

해결책

과정 1

주어진 기능을 실제로 제공하는 적절한 클래스 생성 작업을 별도의 클래스/메서드로 분리시켜야 한다.

public enum SchedulingStrategyID { RESPONSE_TIME, THROUGHPUT, DYNAMIC }
public class SchedulerFactory {
  // 스케줄링 전략에 맞는 객체를 생성
  public static ElevatorScheduler getScheduler(SchedulingStrategyID strategyID) {
    switch (strategyID) {
      case RESPONSE_TIME: // 대기 시간 최소화 전략
        scheduler = new ResponseTimeScheduler();
        break;
      case THROUGHPUT: // 처리량 최대화 전략
        scheduler = new ThroughputScheduler();
        break;
      case DYNAMIC: // 동적 스케줄링
        // 0..23
        int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        // 오전: 대기 시간 최소화, 오후: 처리량 최대화
        if (hour < 12)
          scheduler = new ResponseTimeScheduler();
        else
          scheduler = new ThroughputScheduler();
        break;
    }
    return scheduler;
  }
}
public class ElevatorManager {
  private List<ElevatorController> controllers;
  private SchedulingStrategyID strategyID;

  // 주어진 수만큼의 ElevatorController를 생성함
  public ElevatorManager(int controllerCount, SchedulingStrategyID strategyID) {
    // 엘리베이터의 이동을 책임지는 ElevatorController 객체 생성
    controllers = new ArrayList<ElevatorController>(controllerCount);
    for (int i=0; i<controllerCount; i++) {
      ElevatorController controller = new ElevatorController(i + 1);
      controllers.add(controller);
    }
  }
  // 실핼 중에 다른 스케줄링 전략으로 지정 가능
  public setStrategyID(SchedulingStrategyID strategyID) {
    this.strategyID = strategyID;
  }
  // 요청에 따라 엘리베이터를 선택하고 이동시킴
  void requestElevator(int destination, Direction direction) {
    // 주어진 전략 ID에 해당되는 ElevatorScheduler를 사용함 (변경)
    ElevatorScheduler scheduler = SchedulerFactory.getScheduler(strategyID);
    System.out.println(scheduler);

    // 주어진 전략에 따라 엘리베이터를 선택함
    int selectedElevator = scheduler.selectElevator(this, destination, direction);
    // 선택된 엘리베이터를 이동시킴
    controllers.get(selectElevator).gotoFloor(destination);
  }
}
public class Client {
  public static void main(String[] args) {
    ElevatorManager emWithResponseTimeScheduler = new ElevatorManager(2, SchedulingStrategyID.RESPONSE_TIME);
    emWithResponseTimeScheduler.requestElevator(10, Direction.UP);

    ElevatorManager emWithThroughputScheduler = new ElevatorManager(2, SchedulingStrategyID.THROUGHPUT);
    emWithThroughputScheduler.requestElevator(10, Direction.UP);

    ElevatorManager emWithDynamicScheduler = new ElevatorManager(2, SchedulingStrategyID.DYNAMIC);
    emWithDynamicScheduler.requestElevator(10, Direction.UP);
  }
}
출력 결과
ResponseTimeScheduler@2d74e4b3
Elevator [2] Floor: 1 ==> 10
ThroughputScheduler@500c05c2
Elevator [1] Floor: 1 ==> 10
ThroughputScheduler@5e6a1140
Elevator [1] Floor: 1 ==> 10

과정 2

동적 스케줄링 방식(DynamicScheduler)이라고 하면 여러 번 스케줄링 객체를 생성하지 않고 한 번 생성한 것을 계속해서 사용하는 것이 바람직할 수 있다.

public class SchedulerFactory {
  // 스케줄링 전략에 맞는 객체를 생성
  public static ElevatorScheduler getScheduler(SchedulingStrategyID strategyID) {
    ElevatorScheduler scheduler = null; // 각 전략에 의해 할당됨

    switch (strategyID) {
      case RESPONSE_TIME: // 대기 시간 최소화 전략
        scheduler = ResponseTimeScheduler.getInstance();
        break;
      case THROUGHPUT: // 처리량 최대화 전략
        scheduler = ThroughputScheduler.getInstance();
        break;
      case DYNAMIC: // 동적 스케줄링
        // 0..23
        int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        // 오전: 대기 시간 최소화, 오후: 처리량 최대화
        if (hour < 12)
          scheduler = ResponseTimeScheduler.getInstance();
        else
          scheduler = ThroughputScheduler.getInstance();
        break;
    }
    return scheduler;
  }
}
/* 싱글턴 패턴으로 구현한 ThroughputScheduler 클래스 */
public class ThroughputScheduler {
  private static ElevatorScheduler scheduler;
  // 생성자를 private으로 정의
  private ThroughputScheduler() {}
  // 정적 메서드로 객체 생성을 구현 (싱글턴 패턴)
  public static ElevatorScheduler getInstance() {
    if(scheduler == null)
      scheduler = new ThroughputScheduler();
    return scheduler;
  }
  public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
    return 0; // 임의로 선택함
  }
}
/* 싱글턴 패턴으로 구현한 ResponseTimeScheduler 클래스 */
public class ResponseTimeScheduler {
  private static ElevatorScheduler scheduler;
  // 생성자를 private으로 정의
  private ResponseTimeScheduler() {}
  // 정적 메서드로 객체 생성을 구현 (싱글턴 패턴)
  public static ElevatorScheduler getInstance() {
    if(scheduler == null)
      scheduler = new ResponseTimeScheduler();
    return scheduler;
  }
  public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
    return 1; // 임의로 선택함
  }
}
출력 결과
ResponseTimeScheduler@5878ae82
Elevator [2] Floor: 1 ==> 10
ThroughputScheduler@5552bb15 // 동일 객체
Elevator [1] Floor: 1 ==> 10
ThroughputScheduler@5552bb15 // 동일 객체
Elevator [1] Floor: 1 ==> 10

다른 방법으로 팩토리 메서드 패턴 적용하기

  1. Factory 클래스 이용
    • SchedulerFactory 클래스에서 3가지 방식(최대 처리량, 최소 대기 시간, 동적 선택)에 맞춰 ThroughputScheduler 객체나 ResponseTimeScheduler 객체를 생성
  2. 상속 이용
    • 해당 스케줄링 전략에 따라 엘리베이터를 선택하는 클래스를 ElevatorManager 클래스의 하위 클래스로 정의

상속을 이용

하위 클래스에서 적합한 클래스의 객체를 생성하여 객체의 생성 코드를 분리한다.

/* 템플릿 메서드를 정의하는 클래스: 하위 클래스에서 구현될 기능을 primitive 메서드로 정의 */
public abstract class ElevatorManager {
  private List<ElevatorController> controllers;

  // 주어진 수만큼의 ElevatorController를 생성함
  public ElevatorManager(int controllerCount) {
    // 엘리베이터의 이동을 책임지는 ElevatorController 객체 생성
    controllers = new ArrayList<ElevatorController>(controllerCount);
    for (int i=0; i<controllerCount; i++) {
      ElevatorController controller = new ElevatorController(i + 1);
      controllers.add(controller);
    }
  }
  // 팩토리 메서드: 스케줄링 전략 객체를 생성하는 기능 제공
  protected abstract ElevatorScheduler getScheduler();

  // 템플릿 메서드: 요청에 따라 엘리베이터를 선택하고 이동시킴
  void requestElevator(int destination, Direction direction) {
    // 하위 클래스에서 오버라이드된 getScheduler() 메서드를 호출함 (변경)
    ElevatorScheduler scheduler = getScheduler(); // primitive 또는 hook 메서드
    System.out.println(scheduler);

    // 주어진 전략에 따라 엘리베이터를 선택함
    int selectedElevator = scheduler.selectElevator(this, destination, direction);
    // 선택된 엘리베이터를 이동시킴
    controllers.get(selectElevator).gotoFloor(destination);
  }
}
/* 처리량 최대화 전략 하위 클래스 */
public class ElevatorManagerWithThroughputScheduling extends ElevatorManager {
  public ElevatorManagerWithThroughputScheduling(int controllerCount) {
    super(controllerCount); // 상위 클래스 생성자 호출
  }
  // primitive 또는 hook 메서드
  @Override
  protected ElevatorScheduler getScheduler() {
    ElevatorScheduler scheduler = ThroughputScheduler.getInstance();
    return scheduler;
  }
}

/* 대기 시간 최소화 전략 하위 클래스 */
public class ElevatorManagerWithResponseTimeScheduling extends ElevatorManager {
  public ElevatorManagerWithResponseTimeScheduling(int controllerCount) {
    super(controllerCount); // 상위 클래스 생성자 호출
  }
  // primitive 또는 hook 메서드
  @Override
  protected ElevatorScheduler getScheduler() {
    ElevatorScheduler scheduler = ResponseTimeScheduler.getInstance();
    return scheduler;
  }
}

/* 동적 스케줄링 전략 하위 클래스 */
public class ElevatorManagerWithDynamicScheduling extends ElevatorManager {
  public ElevatorManagerWithDynamicScheduling(int controllerCount) {
    super(controllerCount); // 상위 클래스 생성자 호출
  }
  // primitive 또는 hook 메서드
  @Override
  protected ElevatorScheduler getScheduler() {
    ElevatorScheduler scheduler = null;

    // 0..23
    int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
    // 오전: 대기 시간 최소화, 오후: 처리량 최대화
    if (hour < 12)
      scheduler = ResponseTimeScheduler.getInstance();
    else
      scheduler = ThroughputScheduler.getInstance();

    return scheduler;
  }
}

상속을 이용한 팩토리 메서드 패턴 적용

관련된 Post

References