JUINTINATION

팩토리 메서드(Factory Method) 패턴 본문

JAVA객체지향디자인패턴

팩토리 메서드(Factory Method) 패턴

DEOKJAE KWON 2023. 7. 16. 15:31
반응형

팩토 메서드 패턴이란?

상황에 따라 객체를 다르게 생성해야 할 때 1개의 클래스가 아닌 코드를 각각의 하위 클래스에서 재정의하게 하여 객체의 생성 코드를 별도의 클래스/메소드로 분리하는 패턴이다.

다음과 같이 엘레베이터 제어 시스템에서 다양한 엘레베이터 스케줄링을 지원한다고 하자.

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

enum Direction {
    UP,
    DOWN;
}

class ElevatorManager {
    private List<ElevatorController> controllers;
    private ThroughputScheduler scheduler;
    public ElevatorManager(int controllerCount) {
        controllers = new ArrayList<>(controllerCount);
        for (int i = 0; i < controllerCount; i++) {
            controllers.add(new ElevatorController(i + 1));
        }
        scheduler = new ThroughputScheduler();
    }
    public void requestElevator(int destination, Direction direction) {
        int selectedElevator = scheduler.selectElevator(this, destination, direction);
        controllers.get(selectedElevator).gotoFloor(destination);
    }
}

class ElevatorController {
    private int 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;
    }
}

class ThroughputScheduler {
    public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
        return 0; // 임의로 선택한다.
    }
}
  • 위 코드의 문제점
    • ThroughputScheduler가 아닌 스케줄링 전략을 사용해야 한다면 기존 코드를 수정해야 한다.
      • 유지 보수의 어려움
      • OCP 위반

위 문제를 해결하기 위해 스트래티지 패턴을 적용해보았다.

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

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

class ResponseTimeScheduler implements ElevatorScheduler {
    @Override
    public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
        return 0; // 임의로 선택한다.
    }
}

class ElevatorManager {
    private List<ElevatorController> controllers;
    private ElevatorScheduler scheduler;
    public ElevatorManager(int controllerCount) {
        controllers = new ArrayList<>(controllerCount);
        for (int i = 0; i < controllerCount; i++) {
            controllers.add(new ElevatorController(i + 1));
        }
        scheduler = new ThroughputScheduler();
    }
    public void requestElevator(int destination, Direction direction) {
        int selectedElevator = scheduler.selectElevator(this, destination, direction);
        controllers.get(selectedElevator).gotoFloor(destination);
    }
    public void setScheduler(ElevatorScheduler scheduler) {
        this.scheduler = scheduler;
    }
}
  • 위 코드의 문제점
    • 오전/오후에 따라 스케줄링 방식을 프로그램 실행 중에 바꿔야 한다면 기존 코드를 수정해야 한다.

예를 들어 다음과 같이 오전/오후에 따라 스케줄링 방식을 바꾸도록 코드를 수정하면 문제가 발생한다.

class ElevatorManager {
    private List<ElevatorController> controllers;
    private ElevatorScheduler scheduler;
    public ElevatorManager(int controllerCount) {
        controllers = new ArrayList<>(controllerCount);
        for (int i = 0; i < controllerCount; i++) {
            controllers.add(new ElevatorController(i + 1));
        }
    }
    public void requestElevator(int destination, Direction direction) {
        // 엘레베이터 스케줄링 전략의 추가 및 동적 선택 방식 변경이 필요하면 스케줄링 클래스를 직접 생성하도록 기존 코드를 수정해야 한다.
        int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        if (hour < 12) {
            setScheduler(new ResponseTimeScheduler());
        } else {
            setScheduler(new ThroughputScheduler());
        }
        int selectedElevator = scheduler.selectElevator(this, destination, direction);
        controllers.get(selectedElevator).gotoFloor(destination);
    }
    public void setScheduler(ElevatorScheduler scheduler) {
        this.scheduler = scheduler;
    }
}

 

이를 해결하기 위해 스케줄링 전략에 맞는 객체를 생성하는 코드를 별도로 정의한다.

enum SchedulingStrategyID {
    THROUGHPUT,
    RESPONSE_TIME,
    DYNAMIC
}

class SchedulerFactory {
    public static ElevatorScheduler getScheduler(SchedulingStrategyID strategyID) {
        ElevatorScheduler scheduler = null;
        switch (strategyID) {
            case THROUGHPUT:
                scheduler = new ThroughputScheduler();
                break;
            case RESPONSE_TIME:
                scheduler = new ResponseTimeScheduler();
                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;
    }
}

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++) {
            controllers.add(new ElevatorController(i + 1));
        }
        setStrategyID(strategyID);
    }
    public void requestElevator(int destination, Direction direction) {
        ElevatorScheduler scheduler = SchedulerFactory.getScheduler(strategyID);
        System.out.println(scheduler);
        int selectedElevator = scheduler.selectElevator(this, destination, direction);
        controllers.get(selectedElevator).gotoFloor(destination);
    }
    public void setStrategyID(SchedulingStrategyID strategyID) {
        this.strategyID = strategyID;
    }
}

전체 코드를 보면 다음과 같다.

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

enum Direction {
    UP,
    DOWN;
}

enum SchedulingStrategyID {
    THROUGHPUT,
    RESPONSE_TIME,
    DYNAMIC
}

class SchedulerFactory {
    public static ElevatorScheduler getScheduler(SchedulingStrategyID strategyID) {
        ElevatorScheduler scheduler = null;
        switch (strategyID) {
            case THROUGHPUT:
                scheduler = new ThroughputScheduler();
                break;
            case RESPONSE_TIME:
                scheduler = new ResponseTimeScheduler();
                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;
    }
}

interface ElevatorScheduler {
    int selectElevator(ElevatorManager manager, int destination, Direction direction);
}

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

class ResponseTimeScheduler implements ElevatorScheduler {
    @Override
    public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
        return 0; // 임의로 선택한다.
    }
}

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++) {
            controllers.add(new ElevatorController(i + 1));
        }
        setStrategyID(strategyID);
    }
    public void requestElevator(int destination, Direction direction) {
        ElevatorScheduler scheduler = SchedulerFactory.getScheduler(strategyID);
        System.out.println(scheduler);
        int selectedElevator = scheduler.selectElevator(this, destination, direction);
        controllers.get(selectedElevator).gotoFloor(destination);
    }
    public void setStrategyID(SchedulingStrategyID strategyID) {
        this.strategyID = strategyID;
    }
}

class ElevatorController {
    private int 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;
    }
}

또한 스케줄링 전략을 바꿀 때마다 새로운 객체를 생성할 필요가 없으니 싱글턴 패턴까지 적용하면 다음과 같다.

class SchedulerFactory {
    public static ElevatorScheduler getScheduler(SchedulingStrategyID strategyID) {
        ElevatorScheduler scheduler = null;
        switch (strategyID) {
            case THROUGHPUT:
                scheduler = ThroughputScheduler.getInstance();
                break;
            case RESPONSE_TIME:
                scheduler = ResponseTimeScheduler.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;
    }
}

interface ElevatorScheduler {
    int selectElevator(ElevatorManager manager, int destination, Direction direction);
}

class ThroughputScheduler implements ElevatorScheduler {
    private static ElevatorScheduler scheduler = new ThroughputScheduler();
    private ThroughputScheduler() {};
    public static ElevatorScheduler getInstance() {
        return scheduler;
    }
    @Override
    public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
        return 0; // 임의로 선택한다.
    }
}

class ResponseTimeScheduler implements ElevatorScheduler {
    private static ElevatorScheduler scheduler = new ResponseTimeScheduler();
    private ResponseTimeScheduler() {};
    public static ElevatorScheduler getInstance() {
        return scheduler;
    }
    @Override
    public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
        return 0; // 임의로 선택한다.
    }
}

다음은 상속을 이용한 팩토리 메서드 패턴의 적용이다.

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

enum Direction {
    UP,
    DOWN;
}

enum SchedulingStrategyID {
    THROUGHPUT,
    RESPONSE_TIME,
    DYNAMIC
}

interface ElevatorScheduler {
    int selectElevator(ElevatorManager manager, int destination, Direction direction);
}

class ThroughputScheduler implements ElevatorScheduler {
    private static ElevatorScheduler scheduler = new ThroughputScheduler();
    private ThroughputScheduler() {};
    public static ElevatorScheduler getInstance() {
        return scheduler;
    }
    @Override
    public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
        return 0; // 임의로 선택한다.
    }
}

class ResponseTimeScheduler implements ElevatorScheduler {
    private static ElevatorScheduler scheduler = new ResponseTimeScheduler();
    private ResponseTimeScheduler() {};
    public static ElevatorScheduler getInstance() {
        return scheduler;
    }
    @Override
    public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
        return 0; // 임의로 선택한다.
    }
}

abstract class ElevatorManager {
    private List<ElevatorController> controllers;
    private SchedulingStrategyID strategyID;
    public ElevatorManager(int controllerCount) {
        controllers = new ArrayList<>(controllerCount);
        for (int i = 0; i < controllerCount; i++) {
            controllers.add(new ElevatorController(i + 1));
        }
    }
    protected abstract ElevatorScheduler getScheduler(); // primitive 메서드
    public void requestElevator(int destination, Direction direction) { // 템플릿 메서드
        ElevatorScheduler scheduler = getScheduler();
        System.out.println(scheduler);
        int selectedElevator = scheduler.selectElevator(this, destination, direction);
        controllers.get(selectedElevator).gotoFloor(destination);
    }
}

class ElevatorManagerWithThroughputScheduler extends ElevatorManager {
    public ElevatorManagerWithThroughputScheduler(int controllerCount) {
        super(controllerCount);
    }
    @Override
    protected ElevatorScheduler getScheduler() {
        return ThroughputScheduler.getInstance();
    }
}

class ElevatorManagerWithResponseTimeScheduler extends ElevatorManager {
    public ElevatorManagerWithResponseTimeScheduler(int controllerCount) {
        super(controllerCount);
    }
    @Override
    protected ElevatorScheduler getScheduler() {
        return ResponseTimeScheduler.getInstance();
    }
}

class ElevatorManagerWithDynamicScheduler extends ElevatorManager {
    public ElevatorManagerWithDynamicScheduler(int controllerCount) {
        super(controllerCount);
    }
    @Override
    protected ElevatorScheduler getScheduler() {int hour = Calendar.getInstance().get(Calendar.HOUR_OF_DAY);
        if (hour < 12) {
            return ResponseTimeScheduler.getInstance();
        } else {
            return ThroughputScheduler.getInstance();
        }
    }
}

class ElevatorController {
    private int 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;
    }
}

팩토리 메서드 패턴 컬레보레이션

 

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

ConcreteProduct : 구체적으로 객체가 생성되는 Product 하위 클래스

Creator : 팩토리 메서드를 갖는 추상 클래스

ConcreteCreator : 팩토레 메서드를 구현하는 ConcreteProduct 객체를 생성하는 Creator 하위 클래스

728x90
Comments