팩토리 메소드 패턴: 객체의 생성 코드를 별도의 클래스, 메소드로 분리함으로써 객체 생성 방식의 변화를 대비하는데 유용한 패턴
기존 코드: ElevatorManager는 여러 엘리베이터 중에서 스케줄링에 따라서 하나의 엘리베이터를 선택하고 이동시키는 클래스이다. ThroughputScheduler는 처리량을 기준으로 엘리베이터를 선택하는 클래스이다. ElevatorController는 하나의 엘리베이터 이동을 제어하는 클래스이다.
class ThroughputScheduler{ public int selectElevator(ElevatorManager manager, int destination, Direction direction) { return 0; // 임의로 선택한다 } } class ElevatorController{ private int 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); // 현재 층 갱신, 주어진 층으로 엘리베이터가 이동함 curFloor=destination; System.out.println("==>"+curFloor); } } 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); controllers.add(controller); } scheduler=new ThroughputScheduler(); } void requestElevator(int destination, Direction direction) { // ThroughtpusScheduler를 이용해서 엘리베이터를 선택함 int selectedElevator=scheduler.selectElevator(this,destination,direction); // 선택된 엘리베이터를 이동시킴 controllers.get(selectedElevator).gotoFloor(destination); } }
문제점: 현재 ElevatorManager는 ThroughputScheduler를 이용하고 있다. 그런데 만약 다른 스케줄링 전략을 사용해야 한다면 기존 코드를 수정해야만 한다. 이는 OCP에 위배된다.
일반적으로 다양한 전략을 사용하려면 ‘스트래티지 패턴’을 이용하는 것이 좋다.
그러나 만약 동적으로 전략을 선택한다면 어떻게 될까?
예를 들어 오전에는 ThroughputMinScheduler를 사용하고 오후에는 ThroughputMaxScheduler를 사용한다면?
이 때 동적으로 선택하는 방식이 변경된다면 이 역시 OCP에 위배될 수 있다.
해결 방법: 스케줄링 전략에 맞는 객체를 생성하는 코드를 별도로 정의하면 된다!
enum SchedulingStrategyID { RESPONSE_TIME, THROUGHPUT, DYNAMIC } class SchedulerFactory{ public static ElevatorScheduler getScheduler(SchedulingStrategyID strategyID) { ElevatorScheduler scheduler=null; switch(strategyID) { case RESPONSE_TIME: scheduler=new ResponseTimeScheduler(); break; case THROUGHPUT: scheduler=new ThroughtputScheduler(); break; case DYNAMIC: int hour=Calender.getInstance().get(Calender.HOUR_OF_DAY); if(hour<12) scheduler=new ResponseTimeScheduler(); else scheduler=new ThroughputScheduler(); break; } return scheduler; } } class ElevatorController{ private int 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); // 현재 층 갱신, 주어진 층으로 엘리베이터가 이동함 curFloor=destination; System.out.println("==>"+curFloor); } } class ElevatorManager{ private List<ElevatorController> controllers; private SchedulingStrategyID strategyID; public ElevatorManager(int controllerCount, SchedulingStrategyID strategyID) { controllers=new ArrayList<ElevatorController>(controllerCount); for(int i=0; i<controllerCount; i++) { ElevatorController controller=new ElevatorController(i+1); 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); System.out.println(scheduler); 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); emWithResponseTimtScheduler.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); } }