디자인패턴 - 팩토리 메소드 패턴(Factory Method pattern)

2019. 8. 19. 12:10프로그래밍언어/디자인패턴

factory는 '공장'이란 뜻이고, 공장은 물건을 만드는 곳이다. 여기서 물건에 해당되는 것이 바로 인스턴스이다. factory method 패턴은 상위 클래스에서 객체를 생성하는 인터페이스를 정의하고, 하위 클래스에서 인스턴스를 생성하도록 하는 방식이다. 즉 상위 클래스에서는 인스턴스를 만드는 방법만 결정하고, 구체적인 클래스 이름은 뒤로 미룬다. 따라서 객체를 생성하는 인터페이스와 실제 객체를 생성하는 클래스를 분리할 수 있다. 예를 들어 그림처럼 추상 단계에서는 생성하려는 객체의 클래스를 정확히 지정하지 않고, concreteCreator 클래스에서 인스턴스를 생성한다.

 

 

다시 쉽게 정리하면 특정 클래스에서 사용할 클래스를 직접 new로 생성하면 강한 결합이 생기게 된다. 즉, 무엇인가 전략이 바뀔때마다 코드의 변경이 생기게 된다. 여기서 핵심은 사용할 인스턴스를 생성하는 코드를 특정 클래스가 생성하도록 함으로써 클래스 간의 결합도를 낮추게 된다는 것이다. 위의 그림에서 전략은 Product로 볼 수 있고, concreteCreator가 적절한 Product를 생성하는 역할을 담당하게 된다.

 

하나 예를 들어보자. 건물 내의 엘리베이터는 시간대에 따라 작동 방식이 달라져야한다. 오전 출근시간과 점심시간에는 많은 사람들이 엘리베이터를 사용하게 된다. 그렇다면 이 시간대에는 처리량이 극대화될 수 있는 엘리베이터 스케쥴링방식을 사용해야한다. 그 이후 시간대에는 단순히 기다리는 시간이 최소화되도록 하는 스케쥴링방식을 사용한다. 여기서 또 하나의 조건은 특정 스케쥴링 방식을 직접 선택할 수도 있어야한다.

 

아래와 같이 단순한 구현이 가능하다.

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
public class ElevatorManager {
    
    List<Elevator> elevators;
    
    public ElevatorManager(int count) {
        elevators = new ArrayList<>(count);
        
        IntStream.range(0, count).forEach(i->elevators.add(new Elevator()));
        
    }
    
    public void requestElevator(int destination, Scheduler schedule) {
        
        ElevatorScheduler scheduler = null ;
        
        int hour = LocalDateTime.now().getHour();
        
        if(schedule.equals(Scheduler.DYNAMIC)) {
            System.out.println("스케줄링방식 - "+schedule);
            if( (9<=hour && hour<=10|| (12<=hour && hour<=1)) {
                scheduler = new ThroughputScheduler();
            }else {
                scheduler = new ResponseTimeScheduler();
            }
        }else if(schedule.equals(Scheduler.THROUGHPUT)) {
            System.out.println("스케줄링방식 - "+schedule);
            scheduler = new ThroughputScheduler();
        }else if(schedule.equals(Scheduler.RESPONSETIME)) {
            System.out.println("스케줄링방식 - "+schedule);
            scheduler = new ResponseTimeScheduler();
        }
        
        int elevatorIndex = scheduler.selectElevator(elevatorCount());
        
        elevators.get(elevatorIndex).gotoElevator(destination, elevatorIndex);
    }
    
    private int elevatorCount() {
        return this.elevators.size();
    }
    
    enum Scheduler{
        THROUGHPUT,RESPONSETIME,DYNAMIC;
    }
}
 
public class Elevator {
    
    public void gotoElevator(int destination, int eleNum) {
        System.out.println(eleNum+"번 엘리베이터가 "+destination+"층으로 가는 중입니다.");
    }
}
 
public interface ElevatorScheduler {
    
    public int selectElevator(int count);
    
}
 
public class ThroughputScheduler implements ElevatorScheduler {
 
    @Override
    public int selectElevator(int count) {
        return (int)(Math.random()*count)+1;
    }
 
}
 
public class ResponseTimeScheduler implements ElevatorScheduler {
 
    @Override
    public int selectElevator(int count) {
        return (int)(Math.random()*count)+1;
    }
 
}
cs

 

문제가 무엇일까? 만약에 스케쥴링방식이 추가되거나 혹은 스케쥴링 선택 정책이 변경되었다면 ElevatorManager의 requestEleveator 메소드가 변경되어야 할 것이다. 즉, 특정 상황에 따라 알고리즘을 수행하게 되는 객체(스케쥴러)가 달라지기 때문에 혹은 정책이 바뀌면서 알고리즘을 수행하는 객체가 바뀌게 된다. 그렇다면 이러한 상황에서는 어떻게 하면 좋을까? 바로 팩토리 메소드 패턴이다. 팩토리 메소드 패턴으로 객체 생성을 담당하도록 별도 클래스 및 메소드로 분리하는 것이다. 그렇다면 ElevatorManager는 객체 생성을 직접적으로 신경쓸 필요가 없고 책임을 팩토리 클래스로 위임해버리는 것이다. 

 

팩토리 메소드 패턴으로 변경해보자.

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public class ElevatorManager {
    
    List<Elevator> elevators;
    
    public ElevatorManager(int count) {
        elevators = new ArrayList<>(count);
        
        IntStream.range(0, count).forEach(i->elevators.add(new Elevator()));
        
    }
    
    public void requestElevator(int destination, Scheduler schedule) {
        
        Factory factory = new SchedulerFactory();
        
        ElevatorScheduler scheduler = factory.createScheduler(schedule);
        
        int elevatorIndex = scheduler.selectElevator(elevatorCount());
        
        elevators.get(elevatorIndex).gotoElevator(destination, elevatorIndex);
    }
    
    private int elevatorCount() {
        return this.elevators.size();
    }
    
    enum Scheduler{
        THROUGHPUT,RESPONSETIME,DYNAMIC;
    }
}
 
public abstract class Factory {
    
    public abstract ElevatorScheduler createScheduler(Scheduler scheduler);
    
}
 
public class SchedulerFactory extends Factory{
 
    @Override
    public ElevatorScheduler createScheduler(Scheduler schedule) {
        
        ElevatorScheduler scheduler = null ;
        
        int hour = LocalDateTime.now().getHour();
        
        if(schedule.equals(Scheduler.DYNAMIC)) {
            System.out.println("스케줄링방식 - "+schedule);
            if( (9<=hour && hour<=10|| (12<=hour && hour<=1)) {
                scheduler = new ThroughputScheduler();
            }else {
                scheduler = new ResponseTimeScheduler();
            }
        }else if(schedule.equals(Scheduler.THROUGHPUT)) {
            System.out.println("스케줄링방식 - "+schedule);
            scheduler = new ThroughputScheduler();
        }else if(schedule.equals(Scheduler.RESPONSETIME)) {
            System.out.println("스케줄링방식 - "+schedule);
            scheduler = new ResponseTimeScheduler();
        }
        
        return scheduler;
        
    }
    
    
}
cs

 

위와 같이 코드를 변경하였다. 이제 ElevatorManager는 자신의 일만 신경쓰면 된다. 스케쥴러 선택은 팩토리 클래스에게 맡겨버리면 되기 때문이다. 혹시나 스케쥴링 정책 혹은 새로운 스케쥴링 방식이 생겨나도 다른 곳은 아무곳도 신경쓸 필요가 없다. 단순히 팩토리 클래스에만 변경해주면 되기 때문이다. 

 

다시 한번 팩토리 메서드 패턴의 정의를 보자. 

 

객체를 생성하는 코드를 별도의 클래스/메서드로 분리함으로써 객체 생성 방식의 변화에 대비하는데 유용한 패턴이다.