팩토리 패턴은 인스턴스를 만드는 절차를 추상화한 패턴이다. 팩토리 패턴은 생성 패턴 중 하나로 생성패턴은 객체를 생성, 합성하는 방법이나 객체의 표현 방법을 시스템과 분리해 준다.
생성패턴을 활용하면 객체 생성에 대한 유연성을 확보할 수 있다.
Client의 makeShape()에서 사용자에게 도형을 이름을 입력받아 도형을 생성하는 코드가 있다고 해보자.
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
|
interface Shape {
void draw();
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("draw circle");
}
}
class Square implements Shape {
@Override
public void draw() {
System.out.println("draw sqaure");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("draw rectangle");
}
}
public class Client {
private static String CIRCLE = "circle";
private static String SQUARE = "square";
private static String RECTANGLE = "rectangle";
public void makeShape() {
Scanner scanner = new Scanner(System.in);
String inputtedShape = scanner.next();
Shape shape;
if(inputtedShape.equals(CIRCLE)) {
shape = new Circle();
} else if (inputtedShape.equals(SQUARE)) {
shape = new Square();
} else if (inputtedShape.equals(RECTANGLE)) {
shape = new Rectangle();
}
}
}
|
cs |
이 코드는 클라이언트 쪽에서 Shape의 서브 클래스들과 그들의 생성자를 알아야 한다는 문제를 가지고 있다. 즉, 결합도가 높은 설계이다. 따라서 Shape의 서브 클래스가 추가되거나 생성자 인자에 변경이 발생한다면 클라이언트 까지 수정을 해야 한다.
객체지향 패러다임을 활용해 설계를 할때는 결합도를 낮추어 객체에게 자율성을 부여해야 한다. 객체의 생성 단계에서 결합도는 낮추는 방법 중 하나가 팩토리 패턴을 사용하는 것이다. 팩토리 패턴을 상요하면 인스턴스를 필요로 하는 객체가 서브 클래스에 대한 정보를 모른 채 인스턴스를 생성할 수 있게 해준다.
팩토리 패턴을 활용하면 아래와 같이 설계를 변경해야 한다.
ShapeFactory에서 필요한 서브 클래스의 인스턴스를 생성해 반환한다.
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
|
/* ... */
class ShapeFactory {
private static String CIRCLE = "circle";
private static String SQUARE = "square";
private static String RECTANGLE = "rectangle";
public static Shape getShape(String shapeName) {
if(shapeName.equals(CIRCLE)) {
return new Circle();
} else if (shapeName.equals(SQUARE)) {
return new Square();
} else if (shapeName.equals(RECTANGLE)) {
return new Rectangle();
}
return null;
}
}
public class Client {
public void makeShape() {
Scanner scanner = new Scanner(System.in);
String inputtedShape = scanner.next();
Shape shape = ShapeFactory.getShape(inputtedShape);
}
}
|
cs |
이렇식으로 구현하면 Client는 ShapeFactory에게 메시지만을 전달하는 것으로 인스턴스를 생성할 수 있다. 따라서 결합노를 낮추어 확장이 쉬워진다.
사용 예시
java.util 패키지에 있는 Calendar, NumberFormat 등의 클래스에서 정의된 getInstance() 메서드가 팩토리 패턴을 사용한다.
Wrapper class 안에 정의된 valueOf() 메서드 역시 팩토리 패턴을 사용한다.
정적 팩토리 메서드(static factory method)
정적 팩토리 메서드는 팩토리 메서드에서 파생된 단어이며 객체를 생성하는 역할을 분리하겠다는 의미를 담고 있다. 따라서 정적 팩토리 메서드는 객체 생성의 역할을 하는 클래스 메서드이다. 위 예시에서 ShapeFactory의 getShape는 static 메서드인데. 이 메서드가 정적 팩토리 메서드이다.
정적 팩토리 메서드는 다음과 같은 장점을 가지고 있다.
1. 이름을 사용할 수 있다.
코드를 작성할 때는 이름이 유의미해야 다른 사람이 코드를 이해하기 쉽다. 읽기 쉬운 코드는 버그 발생 가능성을 낮춘다. 이런 측면에 서 정적 팩토리 메서드는 객체 생성에 있어 적절한 역할을 하고 있다. 정적 팩토리 메서드는 메서드 이름에 객체의 생성 목적을 담을 수 있다.
2. 호출때 마다 새로운 객체를 생성할 필요가 없어진다.
enum과 같이 사용되는 요소의 개수가 정해져 있다면. 미리 객체를 생성하고 캐싱할 수 있다. 캐싱을 하게 되면 새로운 객체를 생성하는 부담을 덜 수 있다. 또 한 생성자의 접근 제한을 private으로 설정해 객체 생성을 정적 팩토리 메서드로만 가능하게 제한할 수 있다.
다음은 로또 번호를 생성하는 메서드의 예시이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class LottoNumber {
private static final int MIN_LOTTO_NUMBER = 1;
private static final int MAX_LOTTO_NUMBER = 45;
private static Map<Integer, LottoNumber> cache = new HashMap<>();
static {
IntStream.range(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER)
.forEach(i -> cache.put(i, new LottoNumber(i)));
}
private int number;
private LottoNumber(int number) {
this.number = number;
}
public LottoNumber of(int number) {
return cache.get(number);
}
}
|
cs |
3. 서브 클래스를 모두 알 필요가 없다.
이글에서 정적 팩토리 메서드 이전에 팩토리 패턴에 대해 설명했던 예제를 보자. 팩토리 패턴을 사용하기 전에는 서브 클래스들을 모두 알고 있어야 됬고 서브 클래스가 변경되면 그 객체를 생성하는 부분 역시 클라이언트에서 변경되어야 했다. 이는 강한 결합도를 가진다는 의미로 설계가 잘못되었다는 의미이다. 하지만 정적 팩토리 메서드를 사용하면 결합도를 낮추어 객체에게 자율성을 부여한다. 이는 제대로된 캡슐화가 된다는 것을 의미한다.
팩토리 패턴 메서드 네이밍 컨벤션:
1. from: 하나의 매개변수를 받아 객체를 생성
2. of: 여러개의 매개 변수를 받아 객체를 생성
3. getInstance, instance: 인스턴스 생성, 캐싱된 인스턴스
4. newInstance, instance: 새로운 인스턴스 생성
3. get[OhterType]: 다른 타입의 인스턴스 생성, 캐싱된 인스턴스
4. new[OtherType]: 다른 타입의 새로운 인스턴스 생성