부모 클래스에서 객체들을 생성할 수 있는 인터페이스를 제공하지만
자식 클래스들이 생성될 객체들의 유형을 변경할 수 있도록 하는 생성 패턴
물류 관리 앱 가정
앱 v1 - 트럭 운송만 처리 가능, 대부분의 코드가 Truck 클래스에 있음
앱 v2 - 선박 운송도 처리해야 하는 상황 → 앱에 Ship 클래스를 추가하려면 전체 코드 베이스를 변경해야 함, 추후 다른 교통수단 추가하려면 또 변경 필요
결과 - 많은 조건문이 운송 수단 객체들의 클래스에 따라 앱의 행동을 바꾸는 복잡한 코드 작성됨

products 라고도 부름

Truck과 Ship 클래스들은 모두 deliver라는 메서드를 선언한 Transport 인터페이스를 구현해야 함 → 각 클래스는 deliver를 다르게 구현

팩토리 메서드를 사용하는 코드(클라이언트 코드) - 모든 products를 추상 Transport로 간주, 다양한 자식 클래스들에서 반환되는 여러 products 간 차이를 모름

1. Products - 생성자와 자식 클래스들이 생성할 수 있는 모든 객체에 공통인 인터페이스 선언
2. Concrete Products - product 인터페이스의 다양한 구현들
3. Creator - 새로운 product 객체들을 반환하는 팩토리 메서드 선언
- 팩토리 메서드의 반환 유형이 product 인터페이스와 일치해야 함
- 팩토리 메서드를 abstract로 선언해 자식 클래스들이 자체 버전을 구현하도록 하거나,
기초 팩토리 메서드가 기본 product 유형을 반환하도록 만들 수 있음
- 이름은 creator이지만 주책임은 제품 생성이 아님,
creator 클래스는 이미 핵심 비즈니스 로직 보유
→ 팩토리 메서드가 concrete product 클래스로부터 로직을 디커플링 하는 데 도움을 줌
4. Concrete Creator - 기초 팩터리 메서드를 override해 다른 유형의 제품을 반환하게 하도록 함
- 팩토리 메서드가 항상 새로운 인스턴스들을 생성할 필요는 없음
→ 기존 객체들을 캐시, 객체 풀, 다른 소스로부터 반환하는 것도 가능
- 팩토리 메서드는 product 생성 코드와 product를 사용하는 코드를 분리
→ product 생성자 코드를 독립적으로 확장하기 쉬워짐
e.g. 앱에 새로운 제품 추가: 새로운 크리에이터 자식 클래스 생성 후 팩토리 메서드 override
- 상속 - 라이브러리나 프레임워크의 기본 행동을 확장하는 가장 쉬운 방법
- 프레임워크가 표준 컴포넌트 대신 자식 클래스를 사용해야 하는지 아는 법:
프레임워크 전체에서 컴포넌트들을 생성하는 코드를 단일 팩토리 메서드로 합치고,
누구나 이 팩토리 메서드를 override할 수 있도록 함
e.g. 오픈 소스 UI 프레임워크:
프레임워크 UIFramework에서 사각형 버튼 Button만 지원하는데
둥근 버튼 RoundButton이 필요한 경우
→ 기초 프레임워크 클래스에서 자식 클래스 UIWithRoundButtons 생성
→ 기초 프레임워크 클래스의 createButton 메서드를 override해
자식 클래스가 RoundButton 객체를 반환하도록 함
→ UIFramework 대신 UIWithRoundButtons 사용
- DB 연결, 파일 시스템, 네트워크처럼 자원을 많이 사용하는 대규모 객체 처리 시 자주 발생
- 기존 객체를 재사용하려면:
1. 생성된 모든 객체 추적을 위해 일부 스토리지 생성
2. 누군가가 객체를 요청하면 프로그램은 해당 풀 내에서 유휴 객체 검색
3. 이 객체를 클라이언트 코드에 반환
4. 유휴 객체가 없으면 새로운 객체 생성 → 풀에 추가
- 위 코드를 배치할 수 있는 가장 확실하고 편리한 곳 → 재사용하려는 클래스의 생성자
- 하지만 생성자는 특성상 항상 새로운 객체들을 반환해야 함, 이미 존재하는 인스턴스 반환 불가
- 새 객체들을 생성하고 기존 객체를 재사용할 수 있는 일반적인 메서드 → 팩토리 메서드
1. 모든 product가 같은 인터페이스를 따르도록 함
- 이 인터페이스는 모든 product에서 의미가 있는 메서드들을 선언해야 함
2. creator 클래스 내부에 빈 팩토리 메서드 추가
- 이 메서드의 반환 유형은 공통 product 인터페이스와 일치해야 함
3. creator의 코드에서 product 생성자들에 대한 모든 참조를 찾아
하나씩 팩토리 메서드에 대한 호출로 교체하면서 product 생성 코드를 팩토리 메서드로 추출
4. 팩토리 메서드에 나열된 각 product 유형에 대한 creator 자식 클래스들의 집합 생성,
자식 클래스들에서 팩토리 메서드를 override하고 기초 메서드에서 생성자 코드의 적절한 부분들 추출
5. product 유형이 너무 많은 경우 자식 클래스들의 기초 클래스의 제어 매개변수를 재사용
- Mail → AirMail, GroundMail
- Transport → Plane, Truck, Train
- GroundMail이 Truck과 Train 모두 사용할 수 있는 경우
TrainMail같은 새 자식 클래스를 만드는 대신
GroundMail 클래스의 팩토리 메서드에 argument를 전달
6. 추출이 모두 끝난 후 기초 팩토리 메서드가 비어 있으면 추상화 가능,
비어 있지 않으면 나머지를 그 메서드의 디폴트 행동으로 만들 수 있음
- creator와 concrete product들의 결합도 낮음
- SRP - product 생성 코드를 프로그램의 한 위치로 이동해 유지보수성 향상
- OCP - 기존 클라이언트 코드를 훼손하지 않고 새로운 유형의 product 도입 가능
- 패턴 구현을 위해 많은 새로운 자식 클래스들을 도입해야 해서 코드 복잡도 증가
→ 가장 좋은 시나리오는 이미 존재하는 creator 클래스들의 계층구조에 패턴을 도입하는 것
- 많은 디자인은 팩토리 메서드(덜 복잡 & 자식 클래스들을 통해 더 많은 커스터마이징 가능)로 시작해
추상 팩토리, 프로토타입 또는 빌더(더 유연 & 더 복잡) 패턴으로 발전
- 추상 팩토리 클래스 - 팩토리 메서드들의 집합을 기반으로 하는 경우가 많음
- 팩토리 메서드를 이터레이터와 함께 사용해 콜렉션 자식 클래스들이
해당 콜렉션들과 호환되는 다양한 유형의 이터레이터들을 반환하도록 할 수 있음
- 프로토타입 - 상속 기반이 아니라서 상속의 단점 X,
하지만 복제된 객체의 복잡한 초기화 필요,
팩토리 메서드 - 상속 기반이지만 초기화 단계 필요 X
- 팩토리 메서드 - 템플릿 메서드의 특수화
/**
* The Creator class declares the factory method that is supposed to return an
* object of a Product class. The Creator's subclasses usually provide the
* implementation of this method.
*/
abstract class Creator {
/**
* Note that the Creator may also provide some default implementation of the
* factory method.
*/
public abstract factoryMethod(): Product;
/**
* Also note that, despite its name, the Creator's primary responsibility is
* not creating products. Usually, it contains some core business logic that
* relies on Product objects, returned by the factory method. Subclasses can
* indirectly change that business logic by overriding the factory method
* and returning a different type of product from it.
*/
public someOperation(): string {
// Call the factory method to create a Product object.
const product = this.factoryMethod();
// Now, use the product.
return `Creator: The same creator's code has just worked with ${product.operation()}`;
}
}
/**
* Concrete Creators override the factory method in order to change the
* resulting product's type.
*/
class ConcreteCreator1 extends Creator {
/**
* Note that the signature of the method still uses the abstract product
* type, even though the concrete product is actually returned from the
* method. This way the Creator can stay independent of concrete product
* classes.
*/
public factoryMethod(): Product {
return new ConcreteProduct1();
}
}
class ConcreteCreator2 extends Creator {
public factoryMethod(): Product {
return new ConcreteProduct2();
}
}
/**
* The Product interface declares the operations that all concrete products must
* implement.
*/
interface Product {
operation(): string;
}
/**
* Concrete Products provide various implementations of the Product interface.
*/
class ConcreteProduct1 implements Product {
public operation(): string {
return '{Result of the ConcreteProduct1}';
}
}
class ConcreteProduct2 implements Product {
public operation(): string {
return '{Result of the ConcreteProduct2}';
}
}
/**
* The client code works with an instance of a concrete creator, albeit through
* its base interface. As long as the client keeps working with the creator via
* the base interface, you can pass it any creator's subclass.
*/
function clientCode(creator: Creator) {
// ...
console.log('Client: I\'m not aware of the creator\'s class, but it still works.');
console.log(creator.someOperation());
// ...
}
/**
* The Application picks a creator's type depending on the configuration or
* environment.
*/
console.log('App: Launched with the ConcreteCreator1.');
clientCode(new ConcreteCreator1());
console.log('');
console.log('App: Launched with the ConcreteCreator2.');
clientCode(new ConcreteCreator2());
# Output.txt
App: Launched with the ConcreteCreator1.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct1}
App: Launched with the ConcreteCreator2.
Client: I'm not aware of the creator's class, but it still works.
Creator: The same creator's code has just worked with {Result of the ConcreteProduct2}
참고 자료: Refactoring.Guru