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

JAVA/Design Pattern|2018. 5. 27. 22:07

팩토리 메서드 패턴은 객체의 생성 코드를 별도의 클래스/메서드로 분리하는 패턴이다.

이로써 

특정 기능의 구현은 개별 클래스를 통해서 제공을 하도록 설계를 하면서

호출하는 메서드 코드의 중복된 코드 발생과 기능 변경으로 인해 자주 변경되지 않도록 하기 위해서 
사용하는데 효과적이다.


다음 예를 살펴보자.

여러 엘레베이터 조작을위해 다음과 같이 구성이 되어있다고 가정해보자.

엘레베이터의 층수를 관리하는 ElevatorManager 클래스
각 엘레베이터 클래스 ElevatorController 클래스
스케줄 클래스 ThroughputScheduler 클래스




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
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 < controllers.size(); i++) {
            ElevatorController controller = new ElevatorController(i);
            controllers.add(controller);
        }
        scheduler = new ThroughputScheduler();
    }
    
    public void requestElevator(int destination, Direction direction) {
        Scheduler scheduler;
        if (rushHour) {
          scheduler = new RushHourScheduler();
        } else {
          scheduler = new ThroughputScheduler();
        }
        int selectedElevator = scheduler.selectElevator(this, destination, direction);
        controllers.get(selectedElevator).gotoFloor(destination);
    }
 
}
 
 
public class ThroughputScheduler implements Scheduler {
    public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
        return 0;
    }
}
 
public class RushHourScheduler implements Scheduler {
    public int selectElevator(ElevatorManager manager, int destinataion, Direction direction) {
         return 1;
    }
}
 
 
public class ElevatorController {
    private int id;
    private int curFloor;
    
    public ElevatorController(int id) {
        this.id = id;
        this.curFloor = 1;
    }
    
    public void gotoFloor(int destination) {
        System.out.println("Elevator ]" + id + "] Floor " + curFloor);
        curFloor = destination;
        System.out.println(" ==> " + curFloor);
    }
}
package factory;
 
public enum Direction {
    UP,
    DOWN
}
cs




위의 코드에서 보면
requestElevator에서 scheduler는 현재 시간에 따라 스케줄을 다르게 동작시킨다.

Scheduler라는 인터페이스를 사용하여 각 상황에 맞는 스케줄러를 받는 스트래티지 패턴을 사용하였다.

하지만 ElevatorManager 클래스의 requestElevator() 메서드는 엘레베이터 스케줄링 전략이 변경될 때 마다 수정되어야 하는 단점이 있다.

팩토리 메서드 패턴을 이용한 해결방법

먼저

스케줄링 절략에 일치하는 클래스를 생성하는 코드를 requestElevator 메스드에서 분리해 별도의 클래스/메서드를 정의한다.

생성되는 스케줄러 클래스의 경우 계속해서 새로운 객체를 만들필요가 없으므로 싱글톤으로 사용되도록 변경한다.



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
82
83
84
85
86
87
88
package factory;
 
import java.util.ArrayList;
import java.util.List;
 
public class ElevatorManager {
    private List<ElevatorController> controllers;
    private SchedulingID id;
    
    public ElevatorManager(int controllerCount, SchedulingID id) {
        controllers = new ArrayList<ElevatorController>(controllerCount);
        for (int i = 0; i < controllers.size(); i++) {
            ElevatorController controller = new ElevatorController(i);
            controllers.add(controller);
        }
        this.id = id;
    }
    
    public void requestElevator(int destination, Direction direction) {
        int selectedElevator = SchedulerFactory.getScheduler(id).selectElevator(this, destination, direction); 
        controllers.get(selectedElevator).gotoFloor(destination);
    }
 
}
 
package factory;
 
public class RushHourScheduler implements
        Scheduler {
    private static Scheduler scheduler;
    
    public static synchronized Scheduler getInstance() {
        if (scheduler == null) {
            scheduler = new RushHourScheduler();
        }
        
        return scheduler;
    }
    
    public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
        return 1;
    }
 
}
 
package factory;
 
public class ThroughputScheduler implements Scheduler {
    private static Scheduler scheduler;
    
    public static synchronized Scheduler getInstance() {
        if (scheduler == null) {
            scheduler = new ThroughputScheduler();
        }
        
        return scheduler;
    }
    
    public int selectElevator(ElevatorManager manager, int destination, Direction direction) {
        return 0;
    }
 
}
 
package factory;
 
public class SchedulerFactory {
    public static Scheduler getScheduler(SchedulingID id) {
        Scheduler scheduler = null;
        switch (id) {
        case RUSH_HOUR:
            scheduler = RushHourScheduler.getInstance();
            break;
            
        case NORMAL_TIME:
            scheduler = ThroughputScheduler.getInstance();
            break;
        }
        
        return scheduler;
    }
}
package factory;
 
public enum SchedulingID {
    RUSH_HOUR,
    NORMAL_TIME;
}
cs





또다른 팩토리 메스트 패턴의 형태로써 

ElevatorManager 를 추상클래스로 정의하고 
getScheduler를 추상메서드로 정의한다.

그리고 각 Schuler 클래스들이 ElevatorManager를 상속받아 getScheduler를 구현하는 방식으로
설계할 수도 있다.



이런 구조에서 ElevatorManager 추상클래스에 requestElevator 메서드는 
하위의 Scheduler 클래스들의 공통메서드 이므로 템플릿 메스드에 해당된다.




댓글()

추상 팩토리 패턴 (Abstract Factory Pattern)

JAVA/Design Pattern|2018. 5. 27. 22:06

추상 팩토리 패턴(Abstract Factory Pattern)은 관련성 있는 여러 종류의 객체를 일관된 방식으로 생성하는 경우에 유용하다.

아래에서 예를 통해 알 수 있겠지만 
Door와 Motor를 종류에 따라 얻기 위해 Factory 클래스를 정의해서 사용하기 보다는
관련 객체들을 일관성 있게 생성할 수 있도록 Factory 클래스를 정의하는 것이 효과적이다.




예를 들어보자.

[문제상황]
엘레베이터 모터를 움직여야 할 경우

엘레베이터 브랜드에 따라 door와 motor를 별도로 설정해주어야 하는 경우에는 
다음과 같이 별도로 두 개의 과정을 거쳐야해서 불편하다.



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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// door 추상클래스
public abstract class Door {
    private DoorStatus doorStatus;
 
    public Door() {
        this.doorStatus = DoorStatus.CLOSED;
    }
    
    public static Door getDoor(String id){
        switch(id) {
        case "Apple":
            return new AppleDoor();
        case "Samsung":
            return new SamsungDoor();
        default:
            return null;
        }
    }
 
    public DoorStatus getDoorStatus() {
        return doorStatus;
    }
 
    // 동일하게 사용하는 메서드를 정의해놓는 템플릿 메서드
    public void close() {
        if (doorStatus == DoorStatus.CLOSED)
            return;
 
        doClose();
        doorStatus = DoorStatus.CLOSED;
    }
 
    abstract void doClose();
}
 
// 삼성 문
public class SamsungDoor extends
        Door {
 
    @Override
    void doClose() {
        System.out.println("SamsungDoor");
    }
 
}
 
// Apple 문
public class AppleDoor extends
        Door {
 
    @Override
    void doClose() {
        System.out.println();
    }
 
}
 
// 모터 추상클래스
public abstract class Moter {
    protected Door door;
    public Moter() {}
    
    public static Moter CreateMotor(String id) {
        switch(id) {
        case "Apple":
            return new AppleMoter();
        case "Samsung":
            return new SamsungMoter();
 
        default:
            return null;
        }
    }
    
    public void setDoor(Door door) {
        this.door = door;
    }
    
    public DoorStatus getDoorStatus() {
        return this.door.getDoorStatus();
    }
    
    abstract void move(Direction direction);
}
 
// 삼성 모터
public class SamsungMoter extends
        Moter {
 
    @Override
    public void move(Direction direction) {
        if (getDoorStatus() == DoorStatus.OPENED) {
            
        }
        
        System.out.println(direction.name() + " moved by samsung moter.");
    }
}
 
// Apple 모터
public class AppleMoter extends Moter {
 
    @Override
    public void move(Direction direction) {
        System.out.println(direction.name() + " moved by apple moter.");
    }
 
}
 
// 메인 클래스
public class Main {
    public static void main(String[] args) {
        Door door = Door.getDoor("Samsung");
        Moter motor = Moter.CreateMotor("Samsung");
        
        motor.setDoor(door);
        motor.move(Direction.DOWN);
        
        door = Door.getDoor("Apple");
        motor = Moter.CreateMotor("Apple");
        
        motor.setDoor(door);
        motor.move(Direction.UP);
    }
}
cs





[추상 팩토리 메서드 패턴을 이용한 해결방법]

Motor 클래스, Door 클래스를 위한 Factory 클래스를 정의하는 대신 각 
종류에 맞는 통합 Factory를 정의하여 사용하는 것이 더욱 바람직하다.



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
package abstractPattern2;
 
public class ElevatorFactory {
    public static ElevatorFactory getFactory(String id) {
        switch(id) {
        case "Apple":
            return AppleElevatorFactory.getInstance();
        
        case "SamSung":
            return SamSungElevatorFactory.getInstance();
        
        default:
            return null;
        }
    }
}
 
package abstractPattern2;
 
public class SamSungElevatorFactory extends
        ElevatorFactory {
    
    private static SamSungElevatorFactory instance;
 
    public static synchronized SamSungElevatorFactory getInstance() {
        if (instance == null) {
            instance = new SamSungElevatorFactory();
        }
        
        return instance;
    }
    
    public Motor createMotor() {
        return new SamsungMotor();
    }
    
    public Door createDoor() {
        return new SamsungDoor();
    }
}
 
package abstractPattern2;
 
public class AppleElevatorFactory extends
        ElevatorFactory {
    
    private static AppleElevatorFactory instance;
 
    public static synchronized AppleElevatorFactory getInstance() {
        if (instance == null) {
            instance = new AppleElevatorFactory();
        }
        
        return instance;
    }
    
    public Motor createMotor() {
        return new AppleMotor();
    }
    
    public Door createDoor() {
        return new AppleDoor();
    }
}
 
// 나머지 구성들은 위의 예와 동일
cs



위의 그려진 UML에 
빗대어 본다면 ElevatorFactory가  AbstractFactory
ConcreateFactory는 AppleElevatorFactory와 SamsungElevatorFactory
그리고 AbstractProduct는 Motor와 Door
그리고 마지막으로 ConcreatedProduct들은 각 Apple과 Samsung들의 door와 motor들이다.


정리하면.
여러 종속되는 다양한 종류들이 있어 Factory 클래스를 여러개 정의해야 하는 경우에는
큰 부분을 관리하는 Factory를 만들어서 사용하는 것이 더 바람직하다.

출처 : JAVA 객체지향 디자인 패턴 한빛 미디어

댓글()

컴퍼지트 패턴 (Composite pattern)

JAVA/Design Pattern|2018. 5. 27. 22:04

컴포지트 패턴 (Composite pattern)은 부분-전체의 관계를 갖는 객체들을 정의할 때 유용하다.

그리고 클라이언트는 전체와 부분을 구분하지 않고 동일한 인터페이스를 사용할 수 있다,

컴퍼지트 패턴은 크게 다음과 같이 구성된다.

Component : Leaf 클래스와 Composite 클래스에 공통 인터페이스
Leaf: 부품들 (Component 클래스의 하위 클래스)
Composite : 부품들을 사용하는 본체 클래스 여러가지 Leaf 클래스들을 가진다. 또한 Composite 클래스 역시 Component의 하위 클래스로서 여러개의 Composite를 소유할 수도 있다.





예를 들어보자

휴대폰을 조립하기 위해서
다음과 같이 휴대폰을 구성하기위해

부품들의 가격과 전력을 계산하는 구조가 있다고 가정해보자.

Cellphone 클래스는 자신의 부품들을 has a 관계로 가지고 있으며,
전력과 가격을 구하기 위해서 각 부품들의 가격과 전력을 각각 가져와서 구한다.


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
82
83
84
public class Cpu {
    private int price;
    private int power;
    
    public Cpu(int price, int power) {
        this.price = price;
        this.power = power;
    }
    
    public int getPrice() {
        return price;
    }
 
    public void setPrice(int price) {
        this.price = price;
    }
 
    public int getPower() {
        return power;
    }
 
    public void setPower(int power) {
        this.power = power;
    }
 
}
 
 
public class Memory {
    private int price;
    private int power;
 
    public Memory(int price, int power) {
        this.price = price;
        this.power = power;
    }
 
    public int getPrice() {
        return price;
    }
 
    public void setPrice(int price) {
        this.price = price;
    }
 
    public int getPower() {
        return power;
    }
 
    public void setPower(int power) {
        this.power = power;
    }
 
}
 
public class CellPhone extends Component {
    private Cpu cpu;
    private Memory memory;
 
    public int getPrice() {
        return cpu.getPrice() + memory.getPrice();
    }
 
    public int getPower() {
        return cpu.getPower() + memory.getPower();
    }
 
    public Cpu getCpu() {
        return cpu;
    }
 
    public void setCpu(Cpu cpu) {
        this.cpu = cpu;
    }
 
    public Memory getMemory() {
        return memory;
    }
 
    public void setMemory(Memory memory) {
        this.memory = memory;
    }
 
}
cs



하지만 이런경우에는

새로운 부품이 추가될 경우에
CPU, MEMORY와 같이 추가된 부품을 CellPhone 클래스에 추가하고 

getPrice(), getPower()메서드를 수정한다.

하지만 이런경우에는 굉장히 번거롭고 OCP를 위반한다.

[컴퍼지트 패턴을 이용한 해결방법]

새로운 부품이 추가되어도 기존의 getPrice(), getPower() 메소드의 변경이
없이 동작할 수있도록 부품들을 Component 추상 클래스를 상속하게 한 후

이런 Component들을 여러개 소지할 수 있도록 Cellphone을 변경한다.



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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package compisite;
 
import java.util.List;
 
public class CellPhone {
    private List<Component> components;
 
    public int getPrice() {
        int price = 0;
        for (Component component : components) {
            price += component.getPrice();
        }
        return price;
    }
 
    public int getPower() {
        int power = 0;
        for (Component component : components) {
            power += component.getPrice();
        }
        return power;
    }
    
    public void addComponent(Component component) {
        components.add(component);
    }
    
    public void delComponent(Component component) {
        components.remove(component);
    }
}
 
package compisite;
 
public abstract class Component {
    abstract int getPrice();
    abstract int getPower();
 
}
 
package compisite;
 
public class Cpu extends Component {
    private int price;
    private int power;
    
    public Cpu(int price, int power) {
        this.price = price;
        this.power = power;
    }
    
    @Override
    public int getPrice() {
        return price;
    }
 
    public void setPrice(int price) {
        this.price = price;
    }
    
    @Override
    public int getPower() {
        return power;
    }
 
    public void setPower(int power) {
        this.power = power;
    }
 
}
package compisite;
 
public class Memory extends Component {
    private int price;
    private int power;
 
    public Memory(int price, int power) {
        this.price = price;
        this.power = power;
    }
 
    @Override
    public int getPrice() {
        return price;
    }
 
    public void setPrice(int price) {
        this.price = price;
    }
 
    @Override
    public int getPower() {
        return power;
    }
 
    public void setPower(int power) {
        this.power = power;
    }
 
}
cs




이렇게 변경하게 되면 새로운 부품이 추가되어도 
기존의 메소드를 변경해야하는 작업이 없어진다.


이 예제에서
Component는 Component 추상클래스
Leaf는 Cpu, Memory 클래스
Composite는 CellPhone 클래스이다.

여러 공통 클래스들을 소유하면서 그 공통 클래스들의
속성을 뽑아내어 동일한 동작을 해야하는 경우에는 컴포지트 패턴을 사용하면 좋다.

출처 : JAVA 객체지향 디자인 패턴 한빛 미디어

댓글()