티스토리 뷰

디자인 패턴

Factory Method Pattern

기내식은수박바 2021. 12. 4. 15:48
반응형

팩토리 메서드 패턴?

객체 생성 코드를 별도의 클래스 또는 메서드로 분리하여 객체 생성 변화에 효과적으로 대응할 수 있는 디자인 패턴

 

 

등장 배경

여러 대의 엘리베이터가 있다고 생각해보자. 사용자가 버튼 (FloorButton) 을 눌렀을 때, 여러 대의 엘리베이터 중 하나를 선택하여 이동시켜야 한다.

여기서 주어진 요청 (목적지 층과 방향) 을 받았을 때, 여러 대의 엘리베이터 중 하나를 선택하는 것을 '스케줄링' 이라고 한다.

  • 스케줄링은 여러 가지 전략이 있을 수 있다.
    • ex) 목적지 층과 가까우면서 목적지 층의 방향으로 이동 중인 엘리베이터 선택

복수의 엘리베이터를 스케줄링해 엘리베이터를 이동시키는 클래스 다이어그램

  • ElevatorManager 클래스 : 이동 요청을 처리하는 클래스 (ThroughputScheduler 객체, ElevatorController 객체 복수 개를 갖는다.)
  • requestElevator 메서드 : 요청 (목적지 층, 방향) 을 받았을 때, 우선 ThroughputScheduler 클래스의 selectElevator 메서드를 호출해 적절한 엘리베이터를 선택한 뒤, 선택된 엘리베이터에 해당하는 ElevatorController 객체의 gotoFloor 메서드를 호출해 엘리베이터를 이동시킨다.
public class ElevatorManager {
    private List<ElevatorController> controllers;
    private ThroughputScheduler scheduler;
    
    // 주어진 수만큼의 ElevatorController를 생성함
    public Elevatormanager(int controllerCount) {
        controllers = new ArrayList<>(controllerCount);
        
        for (int i = 0; i < controllerCount; ++i) {
            ElevatorController controller = new ElevatorController(i);
            controllers.add(controller);
        }
        
        scheduler = new ThroughputScheduler(); // ThroughputScheduler 객체를 생성함
    }
    
    void requestElevator(int destination, Direction direction) {
        // ThroughputScheduler를 이용해 엘리베이터를 선택함
        int selectedElevator = scheduler.selectElevator(this, destination, direction);
        
        // 선택된 엘리베이터를 이동시킴
        controllers.get(selectedElevator).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.println("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) 현재 ElevatorManager 클래스는 ThroughputScheduler 클래스를 이용한다. 만약 다른 스케줄링 전략을 사용해야 한다면?

우선 다른 스케줄링인 대기 시간을 최소화하는 전략을 수행하기 위한 스케줄링 클래스가 필요하다.

public class ElevatorManager {
    private List<ElevatorController> controllers;
    
    public ElevatorManager(int controllerCount) {
        controllers = new ArrayList<ElevatorController>(controllerCount);
        for (int i = 0; i < controllerCount; ++i) {
            ElevatorController controller = new ElevatorController(i + 1);
            controllers.add(controller);
        }
    }
    
    void requestElevator(int destination, Direction direction) {
        ElevatorScheduler scheduler;
        
        // 0..23
        int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        
        if (hour < 12) // 오전에는 ResponseTimeScheduler를 이용함
            scheduler = new ResponseTimeScheduler();
        else
            scheduler = new ThroughputScheduler();
            
        int selectedElevator = scheduler.selectElevator(this, destination, direction);
        controllers.get(selectedElevator).gotoFloor(destination);
    }
}

스트래티지 패턴을 사용하게 되면서 ElevatorManager 클래스는 ThroughputScheduler 또는 ResponseTimeScheduler 중 한 클래스를 동적으로 선택할 수 있게 되었다.

그러나 ElevatorManager 클래스는 엘리베이터 스케줄링 전략이 변경될 때 requestElevator 메서드도 수정되어야 한다.

결국, 엘리베이터 스케줄링 전략이 추가되거나 동적 스케줄링 방식으로 전략을 선택하도록 변경되면 아래 사항들이 수행되어야 하고 이는 바람직하지 않다.

  • 해당 스케줄링 전략을 지원하는 구체적인 클래스를 생성해야 한다.
  • requestElevator 메서드도 수정해야 한다.

 

 

해결책

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

  • ex) 엘리베이터 스케줄링 전략에 일치하는 클래스를 생성하는 코드를 requestElevator 메서드에서 분리해 별도의 클래스 / 메서드를 정의

public enum SchedulingStrategyID { RESPONSE_TIME, THROUGHPUT, DYNAMIC }

public class SchedulerFactory {
    public static ElevatorScheduler getScheduler(SchedulingStrategyID strategyID) {
        ElevatorScheduler scheduler = null;
        
        switch (strategyID) {
            case RESPONSE_TIME: // 대기 시간 최소화 전략
                scheduler = new ResponseTimeScheduler();
                break;
            case THROUGHPUT:    // 처리량 최대화 전략
                scheduler = new ThroughputScheduler();
                break;
            case DYNAMIC:       // 오전에는 대기 시간 최소화 전략, 오후에는 처리량 최대화 전략
                int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
                if (hour < 12)  // 오전
                    scheduler = new ResponseTimeScheduler();
                else            // 오후
                    scheduler = new ThroughputScheduler();
                break;
        }
        
        return scheduler;
    }
}

SchedulerFactory 클래스의 getScheduler 메서드는 인자로 주어진 SchedulingStrategyID에 따라 적절한 스케줄링 객체를 생성한다.

 

이제 ElevatorManager 클래스의 requestElevator 메서드에서는 직접 스케줄링 클래스를 생성하는 대신 SchedulerFactory 클래스의 getScheduler 메서드를 호출하면 된다.

public class ElevatorManager {
    private List<ElevatorController> controllers;
    private SchedulingStrategyId strategyID;
    
    public Elevatormanager(int controllerCount, SchedulingStrategyId strategyID) {
        controllers = new ArrayList<>(controllerCount);
        
        for (int i = 0; i < controllerCount; ++i) {
            ElevatorController controller = new ElevatorController(i);
            controllers.add(controller);
        }
        
        this.strategyID = strategyID; // 스케줄링 전략을 설정
    }
    
    public void setStrategyID(SchedulingStrategyID strategyID) {
        this.strategyID = strategyID;
    }
    
    void requestElevator(int destination, Direction direction) {
        // 주어진 전략 ID에 해당하는 ElevatorScheduler 사용
        ElevatorScheduler scheduler = schedulerFactory.getScheduler(strategyID);
        
        int selectedElevator = scheduler.selectElevator(this, destination, direction);
        controllers.get(selectedElevator).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);
    }
}

 

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

따라서, 싱글톤 패턴을 사용할 수 있다.

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:
                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 implements ElevatorScheduler {
    private static ElevatorScheduler scheduler;
    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 implements ElevatorScheduler {
    private static ElevatorScheduler scheduler;
    private ResponseTimeScheduler() { };
    
    public static ElevatorScheduler getInstance() {
        if (scheduler == null)
            scheduler = new ResponseTimeScheduler();
        
        return scheduler;
    }
    
    public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
        return 0;
    }
}
반응형

'디자인 패턴' 카테고리의 다른 글

Decorator Pattern  (0) 2021.10.11
Singleton Pattern  (0) 2021.10.02
Template Method Pattern  (0) 2021.09.26
Strategy Pattern  (0) 2021.09.15
댓글
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함