abstract factory는 '추상적인 공장'이란 뜻으로, 밑의 그림과 같이 여러 개의 concrete Product 추상화시킨 것이다. 따라서 구체적인 구현은 concrete Product 클래스에서 이루어진다. abstract factory에서는 사용자에게 인터페이스(API)를 제공하고, 인터페이스만 사용해서 부품을 조립하여 만든다. 즉 추상적인 부품을 조합해서 추상적인 제품을 만든다.

 

 

2019/08/19 - [디자인패턴] - 디자인패턴 - 팩토리 메소드 패턴(Factory Method pattern)

 

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

factory는 '공장'이란 뜻이고, 공장은 물건을 만드는 곳이다. 여기서 물건에 해당되는 것이 바로 인스턴스이다. factory method 패턴은 상위 클래스에서 객체를 생성하는 인터페이스를 정의하고, 하위 클래스에서..

coding-start.tistory.com

 

이전 포스팅에서 다루어봤던 팩토리 메소드 패턴의 확장판이라고 볼수 있는 것이 추상 팩토리 패턴이다. 팩토리 메소드 패턴을 다시 떠올려보자. 엘리베이터는 특정 스케쥴링 방식에 따라 다른 방식으로 동작한다. 즉, 스케쥴러 클래스의 선택에 따라 엘리베이터의 동작이 달라지는 것이다. 하지만 이런 경우를 생각해보자 !

 

엘리베이터 제조업체가 여러개가 있다. LG,Samsung,Hundai 등의 많은 제조업체가 있고 해당 업체의 엘리베이터를 사용하려면 각각 제조업체의 모터,문,렘프 등을 사용해야한다.(이번 예제에서는 각 업체마다 모터, 문만 다룬다.) 경우에 따라 부품의 수가 더 많아 질 수도 있고 제조업체도 더욱 많아 질 수도 있다. 

 

만약 기존에 사용했던 팩토리 메소드 패턴을 사용한다면 어떻게 될까? Motor,Door라는 인터페이스가 있고 각각을 구현한 LGMotor,LGDoor,SamsungMotor,SamsungDoor 등 업체마다 다른 부품에 해당하는 클래스를 생성해주어야 하고 각각을 생성해주는 팩토리 클래스를 만들어주어야 한다.(LGMotorFactor,SamsungMotorFactory,HundaiFactory...) 3개의 업체라면 객체 생성을 담당하는 팩토리 클래스가 6개가 생성된다.(MotorFactory - LG,Samsung,Hundai / DoorFactory - LG,Samsung,Hundai) 딱 봐도 비효율적일 것같다라는 느낌이 든다.

 

여기서 핵심은 특정 제조업체의 엘리베이터를 이용하면 그들의 부품만 사용한다는 뜻이다. 즉, 관련있는 객체(모터,문)들이 제조업에 따라 같이 생성이 된다는 것이다. 팩토리 메소드 패턴은 한종류의 객체를 생성하는 롤을 갖고 있다면 추상 팩토리 패턴은 다수의 연관있는 객체들의 일련의 생성을 담당하고 있다고 보면 된다. 바로 구현해보자.

 

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
public abstract class AbstractFactory {
    
    public abstract Motor createMotor();
    public abstract Door createDoor();
    
    enum Vendor{
        LG,SAMSUNG,HUNDAI;
    }
    
    public static AbstractFactory getFactory(Vendor vendor) {
        
        AbstractFactory factory = null ;
        
        switch (vendor) {
        case LG:
            factory = LGElevatorFactory.getInstance();
            break;
        case SAMSUNG:
            factory = SamsungElevatorFactory.getInstance();
            break;
        case HUNDAI:
            factory = HundaiElevatorFactory.getInstance();
            break;
        default:
            break;
        }
        
        return factory;
        
    }
    
}
 
public class LGElevatorFactory extends AbstractFactory{
    
    private static final LGElevatorFactory FACTORY = new LGElevatorFactory();
    
    public static AbstractFactory getInstance() {
        return FACTORY;
    }
 
    @Override
    public Motor createMotor() {
        return new LGMotor();
    }
 
    @Override
    public Door createDoor() {
        return new LGDoor();
    }
    
}
cs

 

추상 팩토리 클래스를 하나 만들어주고 각 제조사에 해당하는 팩토리클래스를 생성해준다. 이전 팩토리메소드 패턴과 같이 부품마다 별도의 팩토리 클래스를 두는 것이 아니라 각 제조사마다 팩토리 메소드를 만들어서 관련있는 모든 부품에 대한 생성을 제조사 팩토리 클래스에 위임한다. 또한 모든 제조사의 팩토리 메소드는 동일한 타입의 부품을 생성하는 팩토리 메소드를 가지니, 추상 클래스도 추상 팩토리 클래스를 만들어 상속받게 한다.

 

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
77
78
79
80
81
public abstract class Door {
    
    enum DoorStatus{
        OPEN,CLOSE;
    }
    
    public Door() {
        this.status=DoorStatus.CLOSE;
    }
    
    private DoorStatus status;
 
    public DoorStatus getStatus() {
        return status;
    }
 
    public void setStatus(DoorStatus status) {
        this.status = status;
    }
    
    public void open() {
        if(this.status.equals(DoorStatus.OPEN)) return;
        this.status=DoorStatus.OPEN;
        System.out.println("문을 엽니다.");
    }
    
    public void close() {
        if(this.status.equals(DoorStatus.CLOSE)) return;
        this.status=DoorStatus.CLOSE;
        System.out.println("문을 닫습니다.");
    }
    
}
 
public abstract class Motor {
    
    private Door door;
    private MotorStatus status;
    
    public Motor() {
        this.status=MotorStatus.STOP;
    }
    
    enum MotorStatus{
        MOVING,STOP;
    }
    
//템플릿 메소드 패턴 적용.
    public void move() {
        //모터가 이미 움직이고 있다면 아무런 행동을 하지 않는다.
        if(getStatus().equals(MotorStatus.MOVING)) return;
        //문이 열려있다면 닫는다.
        if(door.getStatus().equals(DoorStatus.OPEN)) door.close();
        doMove();
        //모터를 이동중으로 설정한다.
        setStatus(MotorStatus.MOVING);
    }
    
    /*
     * 업체마다 doMove의 행동은 다르다.
     */
    public abstract void doMove();
 
    public MotorStatus getStatus() {
        return status;
    }
 
    public void setStatus(MotorStatus status) {
        this.status = status;
    }
 
    public Door getDoor() {
        return door;
    }
 
    public void setDoor(Door door) {
        this.door = door;
    }
    
    
    
}
cs

 

각 부품에 대한 추상클래스이다. 각 제조사 부품마다 동일한 행동을 하지만 자세한 구동 프로세스가 다를 수 있으므로 모터 추상클래스는 템플릿 메소드 패턴을 이용하여 상속받게 하였다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
public class LGMotor extends Motor{
 
    @Override
    public void doMove() {
        System.out.println("LG motor 가동");
    }
    
    
}
 
public class LGDoor extends Door{
 
}
cs

 

LG모터와 문 코드이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AbstractFactoryMain {
 
    public static void main(String[] args) {
        
        AbstractFactory factory = AbstractFactory.getFactory(Vendor.LG);
        
        Motor motor = factory.createMotor();
        Door door = factory.createDoor();
        motor.setDoor(door);
        
        door.open();
        motor.move();
        door.open();
    }
 
}
cs

 

모터와 문에 대한 클래스의 구현체를 생각할 필요가 없어졌다. 단순히 추상 팩토리 클래스에 벤더값만 전달하면 모든 연관있는 부품에 대한 생성은 각 업체마다의 팩토리 클래스가 담당하게 된다.

 

어떻게 보면 팩토리 메소드 패턴의 확장판이라고 볼수 있다. 추상 팩토리 패턴은 일련의 관련있는 객체의 생성을 캡슐화 혹은 추상화 하기 위해 사용하는 패턴이다. 이제는 클라이언트 코드에서 업체가 바뀌어도 코드의 변경을 필요 없게 되었다. 단순히 어떠한 업체인지 벤더 값만 전달해주고 팩토리 클래스가 생성해주는 모터와 문의 구현체를 몰라도되고 단순히 사용만 하면 된다.

posted by 여성게
:

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는 자신의 일만 신경쓰면 된다. 스케쥴러 선택은 팩토리 클래스에게 맡겨버리면 되기 때문이다. 혹시나 스케쥴링 정책 혹은 새로운 스케쥴링 방식이 생겨나도 다른 곳은 아무곳도 신경쓸 필요가 없다. 단순히 팩토리 클래스에만 변경해주면 되기 때문이다. 

 

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

 

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