팩토리 메서드 패턴
팩토리 메서드 패턴은 객체의 생성 코드를 별도의 클래스나 메서드로 분리함으로써 객체 생성 로직에 유연성을 부여하는 패턴이다.
여러 개의 클래스(A, Z)에서 필요에 따라 클래스 X1과 X2 중 선택해 객체를 생성한다. 만약 X3 클래스를 추가해야하거나 생성 방식이 달라지면, 모든 코드를 찾아 변경을 해야한다. 이를 그림의 오른쪽처럼 템플릿 메서드를 통해 객체 생성을 한 곳에서 관리하는 것이다.
템플릿 메서드 패턴은 위와 같은 방식 말고도 상속 관계를 통해 설계하는 것도 가능하다.
•
Product : 팩토리 메서드로 생성될 객체의 공통 인터페이스
•
ConcreteProduct : 객체가 생성되는 구체 클래스
•
Creator : 팩토리 메서드를 가지는 클래스
•
ConcreteCreator : 팩토리 메서드를 구현하는 클래스로 ConcreteProduct를 생성
엘레베이터 스케줄링
엘레베이터 스케줄링을 구현해보면서 팩토리 메서드 패턴을 적용해보자.
위 설계에 따라 엘레베이터 스케줄링을 코드로 작성하면,
public class ThroughputScheduler {
public int selectElevator(int destination) {
return 0;
}
}
Java
복사
public class ElevatorController {
private int id;
private int currentFloor;
public ElevatorController(final int id) {
this.id = id;
this.currentFloor = 1;
}
public void gotoFloor(int destination) {
// goto floor
}
}
Java
복사
public class ElevatorManager {
private List<ElevatorController> controllers;
private ThroughputScheduler scheduler;
public ElevatorManager(final int controllerCount) {
controllers = IntStream.range(0, controllerCount)
.mapToObj(ElevatorController::new)
.toList();
scheduler = new ThroughputScheduler();
}
public void requestElevator(int destination) {
final int elevator = scheduler.selectElevator(destination);
controllers.get(elevator).gotoFloor(destination);
}
}
Java
복사
이와 같을 것이다.
위 코드에서 ResponseTimeScheduler를 새로 추가하여, 사용량이 많은 오전에는 ThroughputScheduler를 사용하고 오후에는 ResponseTimeScheduler를 사용한다고 가정해보자.
public interface Scheduler {
int selectElevator(int destination);
}
Java
복사
public class ThroughputScheduler implements Scheduler {
public int selectElevator(int destination) {
// ...
}
}
Java
복사
public class ResponseTimeScheduler implements Scheduler {
@Override
public int selectElevator(final int destination) {
// ...
}
}
Java
복사
public class ElevatorManager {
private List<ElevatorController> controllers;
public ElevatorManager(final int controllerCount) {
controllers = IntStream.range(0, controllerCount)
.mapToObj(ElevatorController::new)
.toList();
}
public void requestElevator(int destination) {
final LocalDateTime now = LocalDateTime.now();
final Scheduler scheduler;
if (now.getHour() < 12) {
scheduler = new ThroughputScheduler();
} else {
scheduler = new ResponseTimeScheduler();
}
final int elevator = scheduler.selectElevator(destination);
controllers.get(elevator).gotoFloor(destination);
}
}
Java
복사
이와 같이 작성하게 될 것이다. 이 부분에 전략 패턴을 도입할 수도 있다.
하지만 전략패턴을 도입하더라도 여전히 스케줄링 정책이 바뀔 때마다 requestElevator 메서드도 같이 변경되어야한다.
이를 팩토리 메서드 패턴을 통해 해결할 수 있다.
팩토리 메서드를 두고 스케줄링 정책에 맞는 객체를 생성하는 책임을 부여하는 것이다.
public enum SchedulingStrategyType {
RESPONSE_TIME,
THROUGHPUT,
DYNAMIC
}
Java
복사
public class SchedulerFactory {
public static Scheduler getScheduler(final SchedulingStrategyType schedulingStrategyType) {
return switch (schedulingStrategyType) {
case RESPONSE_TIME -> new ResponseTimeScheduler();
case THROUGHPUT -> new ThroughputScheduler();
case DYNAMIC -> {
final LocalDateTime now = LocalDateTime.now();
if (now.getHour() < 12) {
yield new ResponseTimeScheduler();
} else {
yield new ThroughputScheduler();
}
}
};
}
}
Java
복사
추가적으로 위 코드에서 DynamicScheduler를 분리하고, 각 스케줄러 구현체에 싱글톤 패턴을 적용하는 것이 바람직하다.(코드는 생략)