디자인패턴_Factory Method Pattern of Creational Pattern

2021. 1. 19. 14:392021/JOB DA STUDY

객체 생성 처리를 서브 클래스로 분리해 처리하도록 캡슐화하는 패턴

- 객체의 생성 코드별도의 클래스와 메서드로 분리해 객체 생성의 변화에 대비 가능

- 특정 기능의 구현개별 클래스를 통해 제공되는 것이 바람직한 설계

 

--> 기능의 변경이나 상황에 따른 기능의 선택은 해당 객체를 생성하는 코드의 변경을 초래

--> 상황에 따라 적절한 객체를 생성하는 코드는 자주 중복 가능

--> 객체 생성 방식의 변화는 해당하는 모든 코드 부분을 변경해야 하는 문제 발생

 

 

 

  • Product: Factory Method로 생성될 객체의 공통 인터페이스
  • ConcreteProduct: 구체적으로 객체가 생성되는 클래스
  • Creator: 팩토리 메서드를 갖는 클래스
  • ConcreteCreator: 팩토리 메서드를 구현하는 클래스로 ConcreteProduct 객체를 생성 
1. 객체 생성을 전담하는 별도의 Factory 클래스를 이용한다.
    - 스트래티지 패턴과 싱글톤 패턴을 이용한다.
2. 상속을 이용: 하위 클래스에서 적합한 클래스의 객체를 생성
    - 스트래티지 패턴, 싱글톤 패턴 그리고 템플릿 메서드 패턴을 이용한다.

 

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

 

작업 처리량(Throughput)을 기준으로 한 스케줄링에 따른 엘리베이터 관리

 

스케줄링: 주어진 요청(목적지 충과 방향 = 엘리베이터 버튼)을 받았을 때 여러대의 엘리베이터 중 하나를 선택하는 것

 

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

    // 주어진 수 (controllerCount) 만큼의 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);
        }
   
    //엘리베이터를 스케줄링(엘리베이터 선택) 하기 위한 ThroughputSceduler 객체를 생성
    scheduler = new ThroughputScheduler();
}
    
    //요청에 따라 엘리베이터 선택 & 이동
    void requestElevator(int destination, Direction direction){
        //ThroughputScheduler를 이용해 엘리베이터를 선택
        int selectedElevator = scheduler.selectElevator(this, destination, direction);
        //선택된 엘리베이터를 이동시킴
        controllers.get(selectElevator).gotoFlooer(destination);
    }
}
  • ElevatorManager 클래스

    - 이동 요청을 처리하는 클래스

    - 엘리베이터를 스케줄링 (엘리베이터 선택) 하기 위한 TroughputScheduler 객체를 갖는다.

    - 각 엘리베이터의 이동을 책임지는 ElevatorController 객체를 복수 개 갖는다.

 

  • requestElevator() 메서드

    - 요청 (목적지에 대한 층과 방향)을 받았을 때 우선 ThroughputScheduler 클래스의 selectElevator() 메서드를 호출해 적정한 엘리베이터를 선택한다.

    - 선택된 엘리베이터에 해당하는 ElevatorController 객체의 gotoFloor() 메서드를 호출해 엘리베이터 이동시킨다.

 

public class ElevatorController{
    private int id; //엘리베이터 ID
    private int curFloor; //현재 층

    public ElevatorController(int id){
        this.id = id;
        curFloor = 1;
    }

    public void gotoFloor(int destinaiton) {
        System.out.print("Elevator [" + id + "] Floor:" + curFloor);

        //현재 층 갱신 ( 주어진 목적지 층(destination)으로 엘리베이터 이동
        curFlooer = destination;
        System.out.print("==>" + curFloor);
    }
}
public class ThroughputScheduler {
    public int selectElevator(ElevatorManager manager, int destination, Direction direction){
        return 0; // 임의로 선택함
    }
}
public calss ResponseTimeScheduler{
    public int selectElevator(ElevatorManager manager, int destination, Direction direction){
        return 1; // 임의로 선택함
    }
}

문제점_1

  • 다른 스케줄링 전략을 사용해야 하는 경우

엘리베이터 작업 처리량을 최대화  (ThroughputScheduler 클래스) 시키는 전략이 아닌, 사용자의 대기 시간을 최소화 하는 엘리베이터 전략을 사용하는 경우

 

해결책

public class ElevatorManager{
    private List<ElevatorController> controllers;

    public ElevatorManager(int controllerCount){
        //엘리베이터의 이동을 책임지는 ElevatorController 객체를 생성
        controllers = new ArrayList<ElevatorController>(controllerCount);

         //주어진 수 만큼의 ElevatorController를 생성
        for( int i=0; i< controllerCount; i++){
            ElevatorController controller = new ElevatorController(i+1);
            controllers.add(controller);
        }
    }

    //요청에 따라 엘리베이터를 선택하고 이동
    void requestElevator(int destination, Direction direction){
        ElevatorScheduler scheduler; //인터페이스
        
        int hour = Calendar.getInstance().get(Calender.HOUR_OF_DAY);

        //오전: ResponseTimeScheduler 오후: ThroughputScheduler
        if(hour < 12) {
            scheduler = new ResponseTimeScheduler();
        }
        else{
            scheduler = new ThroughScheduler();
        }

        //ElevatorScheduler 인터페이스를 이용해 엘리베이터 선택
        int selectElevator = scheduler.selectElevator(this, destination, direction);
        controllers.get(selectElevator).gotoFloor(destination);
    }
}

1. ElevatorManager의 requestManager() 메서드가 실행될 때 마다, 현재 시간에 따라 적절한 스케줄링 객체를 생성

2. ElevatorManager 클래스의 입장에서는 여러 스케줄링 전략이 있기 때문에 ElevatorScheduler라는 인터페이스를 사용해 여러 전략들을 캡슐화 하고, 동적으로 선택할 수 있게 한다.

 

문제점_2

  • 프로그램 실행 중에 스케줄링 전략을 변경하는 경우 (동적 스케줄링 지원을 해야 하는 경우 또는 스케줄링 전략이 추가되는 경우)

 ---> 해당 스케줄링 전략을 지원하는 구체적인 클래스를 생성

 ---> ElevatorManager 클래스의 requestElevator() 메서드(1. 엘리베이터 선택, 2. 엘리베이터 이동) 수정

 

EX)  새로운 스케줄링 전략의 추가 = 엘리베이터 노후화 최소화 전략

      동적 스케줄링 방식의 변경 = 오전: 대기시간 최소화, 오후: 처리량 최대화

 

해결책1

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

변경 전 변경 후
ElevatorManager클래스가 직접 ThroughputScheduler 객체와 ResponseTimeScheduler 객체를 생성 SchedulerFactory클래스의 getScheduler()메서드가 전략에 맞는 객체를 생성

 

public enum SchedulingStrategyID { RESPONSE_TIME, THROUGHPUT, DYNAMIC }
public class SchedulerFactory {
    //스케줄링 전략에 맞는 객체 생성
    public static ElevatorScheduler getScheduler(SchedulingStrategyID starategyID){
      swith(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;
  }
}

 

  • ElevatorManager 클래스의 requestElevator()메서드에서는 SchedulerFactory클래스의 getScheduler 메서드를 호출하면 된다.
public class ElevatorManger{
    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(selectedElevator).gotoElevator(destination);
        }
}
public class Client{
    public static void main(String[] args){
        ElevatorManager emWithResponseTimeScheduler = new ElevatorManger(2, SchedulingStrategyID.RESPONSE_TIME);
        emWithResponseTimeScheduler.requestElevator(10, Direction.UP);

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

        ElevatorManager emWithResponseTimeScheduler = new ElevatorManger(2, SchedulingStrategyID.DYNAMIC);
        emWithResponseTimeScheduler.requestElevator(10, Direction.UP);

해결책2

동적 스케줄링 방식(Dynamic Schceduler)은 여러번 스케줄링 객체를 생성하지 않고,

한번 생성한 것을 계속해서 사용하는 것

 

 

  • 싱글톤 패턴을 이용한 엘리베이터 스케줄링 전략 설계

 

 

스케줄링 기능을 제공하는 ResponseTimeScheduler 클래스와 ThroughputScheduler 클래스는 오직 하나의 객체만 생성해서 사용하도록 한다.

-> 생성자를 통해 직접 객체를 생성하는 것은 허용되지 않아야 한다.

 

  • 생성자를 Private으로 정의
  • getInstance()라는 정적 메서드를 이용해 객체 생성
public class SchedulerFactory{
    //스케줄링 전략에 맞는 객체를 생성
    public static ElevatorScheduler getScheduler(SchedlingStrategyID strategyID){
        ElevatorScheduler scheduler = null;

        switch(strateguyID){
            //대기시간 최소 전략
            case RESPONSE_TIME:
                scheduler = ResponseTimeScheduler.getInstance();
                break;
            //처리량 최대화 전략
            case THROUGHPUT:
                scheduler = ThroughputScheduler.getInstance();
            //동적 스케줄링
            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{
    private static ElevatorScheduler scheduler;
    //생성자를 private으로 정의
    private ThroughputScheduler(){}
    //정적 메소드로 객체 생성을 구현 (싱글톤 패턴)
    public static ElevatorScheduler getInstance(){
        if(scheduler == null)
            scheduler = new ThroughputScheduler();
        return scheduler;
    }

    public int selectElevator(ElecatorManager 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;
    }
}

 

 

 

 

객체 생성을 전담하는 별도의 Factory 클래스를 분리해 객체 생성의 변화에 대비

-> Strategy Pattern과 Singleton Pattern을 이용해 Factory Method Pattern을 적용

 

 

  • 상속을 이용한 엘리베이터 스케줄링 전략 설계

 

Strategy Pattern, Singleton Pattern, TemplateMethod Pattern을 이용 -> Factory Method Pattern을 적용

 

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

    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(cntroller);
        }
    }

    //팩토리 메서드: 스케줄링 전략 객체를 생성하는 기능 제공
    protected abstract ElevatorScheduler getScheduler();
    
    //템플릿 메서드: 요청에 따라 엘리베이터를 선택하고 이동시킴
    void requestElevator(int destination, Direction direction){
        //하위 클래스에서 Override된 getScheduler() 메서드를 호출
        ElevatorScheduler scheduler = getScheduler(); //primitive 또는 hook 메서드
        System.out.println(scheduler);
 
        //주어진 전략에 따라 엘리베이터 선택
        int selectedElevator = scheduler.selectElevator(this, destination, direction)
        //선택된 엘리베이터를 이동
        controllers.get(selectElevator).gotoFlooer(destination);
    }
}

  • 팩토리 메서드

- ElevatorManager 클래스의 getScheduler() 메서드

- 스케줄링 전략 객체를 생성하는 기능 제공 (객체 생성을 분리)

 

  • 템플릿 메서드

- ElevatorManager 클래스의 requestElevator() 메서드

- 공통 기능( = 스케줄링 전략 객체 생성 / 엘리베이터 선택 / 엘리베이터 이동 )의 일반 로직 제공

- 하위 클래스에서 구체적으로 정의할 필요가 있는 "스케줄링 전략 객체 생성" 부분은 하위 클래스에서 오버라이드

   (하위 클래스에서 오버라이드될 필요가 있는 메서드는 primitive 또는 hook 메서드라고 부른다.)

 

*** 템플릿 메서드 패턴은 전체적으로는 동일하면서 부분적으로 다른 구문으로 구성된 메서드의 코드 중복을 최소화 ***

 

----------> 팩토리 메서드를 호출하는 상위 클래스의 메서드 = 템플릿 메서드