정의
객체 지향 프로그래밍에서 Factory는 다른 object를 생성하는 object이다.
- factory method:
new
Class object를 반환하는 메소드
예시
// ShapeFactory 클래스
class ShapeFactory {
// 도형을 생성하는 Factory Method
public Shape createShape(String type) {
if (type.equalsIgnoreCase("circle")) {
return new Circle();
} else if (type.equalsIgnoreCase("rectangle")) {
return new Rectangle();
}
return null;
}
}
생성자와 팩토리의 차이
클래스 기반 프로그래밍에서 Factory는 생성자를 추상화한 것이다. 그러나 엄밀하게 일반화한 것은 아니기 때문에 생성자가 팩토리는 아니다. (생성자가 팩토리의 부분 집합은 아니다. 비슷한 것이다)
- 생성자 (Constructor)
- 클래스 하나의 인스턴스를 생성한다
- 클래스 인스턴스화라는 한 가지 방법으로만 객체를 생성한다
- C++에서 정적으로 할당될 수 있는 객체는 컴파일 타임에 메모리 할당이 결정된다
- Java Object는 항상 동적 할당되어서 이 점에서는 팩토리와 차이가 없다
- 팩토리 (Factory)
- 다양한 클래스를 인스턴스화 할 수 있다
1
- Java의 생성자에서는 하위 클래스를 return 할 수 없다
-
class Circle extends Shape {...} class Shape { public Shape(String type) { if(type.equals("circle")) { // ! 불가능한 문법이다. 반드시 Shape 클래스를 return해야 한다. // 이런 로직이 필요할 때 생성자 대신 factory method를 구현하면 된다 return new Circle(); } } }
- 팩토리는 클래스 인스턴스화 외에도 Object pool과 같은 다양한 할당 체계로 객체를 생성할 수 있다
- C++ 에서 팩토리를 이용한 Object 할당은 항상 런타임 시점에 메모리가 결정된다
- 다양한 클래스를 인스턴스화 할 수 있다
1
디자인 패턴
팩토리를 사용하는 디자인 패턴으로는 factory method pattern과 abstract factory pattern이 있다. 책 Design Patterns에는 Factory Pattern이라는 디자인 패턴은 없으나, 일부 출처에서는 팩토리를 사용한 디자인 패턴을 팩토리 패턴이라고 지칭한다. 엄밀하게는 팩토리와 팩토리를 사용한 디자인 패턴은 다른 용어이다. 팩토리는 다른 객체를 생성하는 객체 그 자체를 의미한다.
의의
생성자 대신 팩토리를 사용하면 객체를 생성할 때 다형성(polymorphism)을 사용할 수 있다. 팩토리를 사용하면 객체 생성 로직을 캡슐화하고 특정 하위 클래스를 참조하지 않게 추상화할 수 있다
- 다른 메소드는 Method dispatching을 통해서 하위 클래스의 구현에 따라 다른 로직을 실행할 수 있다. 그러나 생성자는 아직 객체가 만들어지지 않았기 때문에 같은 이름으로 method distpatching이 불가능하다
-
class Shape { public void draw() {} } class Rectangle extends Shape { @Override public void draw() { // 다른 코드 } } Shape rectangle = new Rectangle(); // method dispatching 가능 // .draw()를 호출해서 Shape 코드가 아닌 Rectangle 코드가 실행되게 할 수 있다 rectangle.draw(); // ! method dispatching 불가능 // Shape이 생성자를 가지고 있고, Circle이 overriding해서 생성자를 가지고 있지만 // Shape 생성자를 호출해서 Circle 생성자 코드가 실행되는 것은 불가능하다 // dispatch할 객체가 아직 없기 때문이다 Shape circle = Shape생성자("Circle 인스턴스 return 기대");
-
예를 들어, 신용카드, 네이버페이, 카카오페이 등등 다양한 결제 로직을 추상화했다고 가정해 보자. 추상화를 잘 해서 결제 메소드 레벨에서는 신용카드, 삼성페이 등등 하위 클래스 구현이 보이지 않는다
// Payment payment
Result result = payment.pay();
if (result == Result.SUCCESS) {
// 결제 기록 저장
// do something
}
문제는 생성자이다. 사용자가 선택한 결제 방법에 따라 payment 인스턴스를 만들어야 하는데, 이를 구현하려면 거추장스러운 if문을 써서 인스턴스를 생성해야만 할 것이다.
Payment payment = null;
if (input.method == Method.CARD) {
payment = new CardPayment();
} else if (input.method == Method.NAVER) {
...
}
이 로직을 캡슐화해서 숨긴 것이 factory이다.
public class Payment {
public static Payment of(PaymentMethod method) {
if (method == MethodEnum.NAVER) {
return new NaverPayment();
} else if () {
...
}
}
}
Payment payment = Payment.of(input.method)
Result result = payment.pay();
if (result == Result.SUCCESS) {
// 결제 기록 저장
// do something
}
코드를 팩토리로 리팩토링한 결과 Payment
를 사용하는 코드에서는 하위 클래스의 내용을 참조하지 않고 추상화했다. 추상화했기 때문에 Payment를 생성하는 로직마다 재사용할 수 있다. 그리고 새로운 결제 수단(ex: 라인 페이)가 생긴다면 Payment를 사용하는 코드를 수정하지 않고 Payment factory method만 수정해서 결제 수단을 새로 추가할 수 있다.
또한 객체 생성 과정이 복잡할 때 이를 추상화하고 재사용하기 위해서 Factory를 사용하기도 한다. 예를 들면, 싱글톤 패턴에서 객체를 반환하는 함수가 팩토리이다. 객체가 없으면 생성하고, 객체가 있으면 기존에 생성된 객체를 반환하여 인스턴스가 한 개만 존재하도록 하는 로직을 추상화했다.
언제 사용하는가?
- 객체를 생성하는 코드가 반복될 때. 중복된 생성 코드를 메소드로 만들 때 사용한다.
- 객체를 생성할 때 그 클래스에 포함되어서는 안 되는 리소스에 접근할 때
- 예를 들어 클래스를 생성할 때 외부 리소스에 액세스해야 한다면, 생성자와 팩토리를 통해 객체 생성과 외부 리소스 액세스 코드를 분리할 수 있다
- 객체 생명주기 관리가 애플리케이션에서 동일하게 동작하도록 코드를 중앙 집중화할 때
- Singleton factory
장점과 적용 사례
- 생성자 대신 명확한 이름을 가진 팩토리를 사용할 수 있다
- 생성자는 메소드 이름이 클래스와 같은 이름으로 고정되어 있다. 반면, 팩토리 메소드는 이름을 자유롭게 지을 수 있어서 의미를 명확하게 할 수 있다
- 캡슐화
- 팩토리 메서드는 객체 생성을 캡슐화한다. 이는 생성 프로세스가 매우 복잡한 경우(예: 구성 파일의 설정이나 사용자 입력에 따라 달라지는 경우)에 유용할 수 있다.
- 아래 예제에서는 프로그램이 이미지를 읽을 때 파일 정보를 기반으로 적절한 Class의 리더를 생성하는 로직을 캡슐화했다
public class ImageReaderFactory {
public static ImageReader createImageReader(ImageInputStreamProcessor iisp) {
if (iisp.isGIF()) {
return new GifReader(iisp.getInputStream());
}
else if (iisp.isJPEG()) {
return new JpegReader(iisp.getInputStream());
}
else {
throw new IllegalArgumentException("Unknown image type.");
}
}
}
제약
- 기존 클래스의 생성자를
private
으로 변경하고 팩토리로 리팩토링하면 기존에 생성자를 사용하는 모든 클라이언트 코드가 동작하지 않는다. 생성자의 종류만큼 팩토리를 생성해야 한다. - 생성자를
private
으로 설정하면 이 클래스를 상속할 수 없다 - 생성자를
protected
로 설정하고 확장하면 하위 클래스가 모든 factory method를 다시 구현해야 한다. 그렇지 않으면 factory method가 상위 클래스 타입을return
할 수 있다.- 일부 언어의 reflection 기능을 사용하면 이 이슈를 피할 수 있다
출처
https://en.wikipedia.org/wiki/Factory_(object-oriented_programming)
https://refactoring.guru/design-patterns/factory-comparison
https://tecoble.techcourse.co.kr/post/2020-05-26-static-factory-method/
- 위 예제의 Square, Circle 등 [본문으로]
'Computer Science > Object Oriented Programming' 카테고리의 다른 글
[OOP] 순환 참조 (Circular reference) - 문제점, 해결법 (설계 관점) (0) | 2024.08.30 |
---|---|
[OOP] getter만 포함된 인터페이스를 생성해도 되는가? (0) | 2024.08.19 |