팩토리 메서드 패턴

seanical·2022년 2월 17일
0

java

목록 보기
2/3

팩토리 메서드란

객체를 생성해주는 것을 팩토리클래스에게 맡기고 클라이언트에서 메소드를 통해 팩토리클래스가 생성해주는 객체를 가져오는것이다.

팩토리 메서드 패턴의 예를 들기 위해 엘레베이티관리에 대해 작성하겠습니다.

서론


요청사항 : 관리하는 엘레베이터의 요청이 들어왔을때 관리 엘레베이터 중 하나를 이동시켜 손님의 요청을 수행합니다.

ElevatorManager 코드

package beforefactorymethod;

import java.util.ArrayList;
import java.util.List;

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

  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); // 엘레베이터 관리 대수 추가
    }
    scheduler = new ThroughputScheduler(); 
  }

  void requestElevator(int destination, String direction) {

      // ThroughputScheduler를 이용해 엘리베이터를 선택함
      int selectedElevator = scheduler.selectElevator(this, destination, direction);

      // 선택된 엘리베이터를 이동시킴
      controllers.get(selectedElevator).gotoFloor(destination);

  }
}

ElevatorMangaer 는 자기가 관리할 엘레베이터 대수를 지정하여 관리 엘레베이터로 지정합니다.

관리하는 엘레베이터의 요청이 들어왔을때 관리 엘레베이터 중 하나를 이동시켜 손님의 요청을 수행합니다.

ElevatorController 코드

package beforefactorymethod;

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

엘레베이터 고유의 id를 등록해주고 현재 위치를 표시하는 curFloor를 둡니다.

ThroughputScheduler 코드

package beforefactorymethod;

/* 엘리베이터 작업 처리량을 최대화시키는 전략의 클래스 */
public class ThroughputScheduler{
    public int selectElevator(ElevatorManager manager, int destination, String direction) {
        return 0; // 임의로 선택함
    }
}

관리 엘레베이터 중 하나를 선택하는 알고리즘이 들어가는 클래스입니다.
현재는 관리 엘레베이터 중 첫번째 엘레베이터를 가져와 수행하게 만들어놨습니다.

본문


만약 여기서 ThroughputScheduler이 아닌 새로운 알고리즘의 변경 요청이들어오면 소스코드를 어디를 건드려줘야 할까요

package beforefactorymethod;

import java.util.ArrayList;
import java.util.List;

public class ElevatorManager {
    private List<ElevatorController> controllers;
    private ThroughputScheduler scheduler; // <--- 이 부분

  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);
    }
    scheduler = new ThroughputScheduler(); // <--- 이 부분
  }

  void requestElevator(int destination, String direction) {

     
      int selectedElevator = scheduler.selectElevator(this, destination, direction);

      controllers.get(selectedElevator).gotoFloor(destination);

  }
}

위 코드의 두 부분을 수정해줘야합니다.
또 새로운 알고리즘이 추가되면 해당 부분을 또 수정해야합니다.
이것은 SOLID의 OCP(개방-폐쇄 원칙)에 위배됩니다.

해당 코드를 고쳐봅시다

ElevatorManager 첫번째 변경 코드

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, String direction) {
      ElevatorScheduler scheduler;

      int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);

      if (hour < 12) 
          scheduler = new ResponseTimeScheduler();
      else
          scheduler = new ThroughputScheduler();
      
      int selectedElevator = scheduler.selectElevator(this, destination, direction);
      
      controllers.get(selectedElevator).gotoFloor(destination);

  }

ElevatorScheduler라는 interface를 두어 구현한 ResponseTimeScheduler, ThroughputScheduler가 추가되었습니다.

ElevatorScheduler interface 코드

public interface ElevatorScheduler {
    public int selectElevator(ElevatorManager manager, int destination, String direction);

}

기존 ThroughputScheduler도 변경해줍니다.

변경된 ThroughputScheduler 코드

public class ThroughputScheduler implements ElevatorScheduler{
	@Override
    public int selectElevator(ElevatorManager manager, int destination, String direction) {
        return 0; // 임의로 선택함
    }
}

새로 추가된 ResponseTimeScheduler 코드

public class ResponseTimeScheduler implements ElevatorScheduler {

    @Override
    public int selectElevator(ElevatorManager manager, int destination, String direction) {
        return 1;
    }
}

하지만 아래사진을 보면 아직 여전히 ElevatorManager 코드는 새롭게 추가되는 알고리즘에 영향을 받습니다.

이것도 고쳐나가봅시다.

SchedulerFactory에게 어떤 알고리즘을 선택하는지에대해 역할을 위임합니다.

말그래도 Scheduler 공장입니다. 원하는 Scheduler 객체 정확히는 ThroughputScheduler,ResponseTimeScheduler를 전달 해줍니다.

SchedulerFactory 코드

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;
    }
    }

원하는 전략을 넘겨주면 해당 객체를 생성하여 넘겨줍니다.

ElevatorManager 두번째 변경 코드


package beforefactorymethod;

import java.util.ArrayList;
import java.util.List;

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, String direction) {

      ElevatorScheduler scheduler = SchedulerFactory.getScheduler(strategyID);// <---- 변경부분

      int selectedElevator = scheduler.selectElevator(this, destination, direction);

      controllers.get(selectedElevator).gotoFloor(destination);

  }
}

전략(strategyID)를 추가함으로써 어떤 알고리즘이 작동 하게 변경 시킬 수 있습니다.
ElevatorManager 생성자에 넣어 주입시켜주거나,
setter를 통해 Client에서 변경할 수 있게 코드를 변경했습니다.

  • SOLID의 OCP(개방-폐쇄 원칙) 만족

이제 우리는 어떤 새로운 전략이 추가되어도 ElevatorManager의 코드 부분을 변경해주지 않아도 됩니다.

리팩토링을 할 부분이 더 있다면 ResponseTimeScheduler,ThroughputScheduler를 싱글턴으로 바꾸는 부분이 있을거 같습니다.

정리


이제 ElevatorManager에서는 엘레베이터에 맞는 알고리즘 ElevatorScheduler 객체를 생성하지않고 SchedulerFactory에게 받아옵니다.

 ElevatorScheduler scheduler = SchedulerFactory.getScheduler(strategyID);

getScheduler를 저희는 factorymethod라고 부릅니다.

객체 생성을 전담하는 별도의 Factory 클래스 이용을 하여 해당 함수를 호출하는 Client는 어떤것인지 전혀 상관하지 않는 것입니다.

또한 생성 패턴에는 총 Product, ConcreteProduct, Creator, ConcreteCreator 가 있습니다.
작성한 코드를 맞춰봅시다.


Product
팩토리 메서드로 생성될 객체의 공통 인터페이스
ElevatorScheduler

ConcreteProduct
구체적으로 객체가 생성되는 클래스
ResponseTimeScheduler,ThroughputScheduler

Creator
팩토리 메서드를 갖는 클래스
ElevatorManager

ConcreteCreator
팩토리 메서드를 구현하는 클래스로 ConcreteProduct 객체를 생성
SchedulerFactory

이렇게 서로의 역할을 구분하여 클래스,인터페이스를 생성하면 요구사항 변경시에 유연한 대처를 할 수 있습니다.

0개의 댓글