concrete 클래스 명시 없이 연관 객체들의 모음을 생성할 수 있게 해주는 생성 패턴
가구 매장 시뮬레이터를 만든다고 가정
코드는 다음을 나타내는 클래스들로 구성됨:
Chair + Sofa + CoffeeTable)Modern, Victorian, ArtDeco)
새로운 가구 객체 생성 시 같은 제품군의 다른 객체들과 일치하는 스타일로 만드는 방법 필요
새로운 제품이나 제품군의 변종이 추가될 때 기존 코드를 바꾸고 싶지 않음
먼저 각 제품군의 제품마다 인터페이스를 명시적으로 선언하고, 모든 변종들이 그 인터페이스를 따르도록 함 (e.g. 모든 Chair 변형들은 Chair interface 구현)

다음으로 추상 팩토리 선언
추상 팩토리 - 제품군 내의 각 제품들의 생성 메서드들(e.g. createChair, createSofa)을 가지는 인터페이스, 이 메서드들은 앞서 말한 각 제품의 인터페이스가 나타내는 추상 제품 타입들 반환 (e.g. Chair, Sofa).

그 다음 각 제품군 변종마다 추상 팩토리를 바탕으로 한 별도의 팩토리 클래스 생성 (e.g. ModernChair, ModernSofa…를 만들 수 있는 ModernFurnitureFactory).
클라이언트 코드는 추상 인터페이스를 통해 팩토리와 제품을 다룸 → 클라이언트 코드를 훼손하지 않으면서 클라이언트 코드에 보낼 팩토리 종류와 제품 변종을 자유롭게 바꿀 수 있음
클라이언트가 의자 생성 팩토리를 원하는 경우, 팩토리나 의자의 종류는 관계 없음, 클라이언트는 모든 의자를 추상 Chair 인터페이스로 취급, 어떤 종류의 의자가 반환되든 같은 팩토리 객체가 생성한 다른 가구들과 같은 변종으로 반환
실제 팩토리 객체는 애플리케이션의 초기화 단계에서 생성됨, 생성 전에 앱이 configuration이나 environment에 근거해 팩토리의 종류를 선택

1. 추상 product - product family를 구성하는 연관 제품들의 집합에 대한 인터페이스 선언
2. concrete product - 변종으로 그룹화된 추상 product들의 다양한 구현
- 각 추상 product들은 모든 변종에 의해 구현되어야 함
3. 추상 팩토리 인터페이스 - 각 추상 product를 생성하는 메서드의 집합을 선언
4. concrete 팩토리 - 추상 팩토리의 생성 메서드를 구현
- 각 concrete 팩토리는 하나의 특정 변종에 대응되며 오직 해당 변종의 product들만 생성
5. concrete 팩토리가 concrete product를 인스턴스화하지만,
생성 메소드들의 시그니처들은 대응되는 추상 product들을 반환해야 함
→ 그래야만 팩토리를 사용하는 클라이언트 코드가
팩토리에서 받아오는 product의 특정 변종에 결합되지 않음
- 클라이언트는 추상 인터페이스를 통해 객체들과 소통하는 한,
어떤 concrete 팩토리/product 변종이든 다룰 수 있음
- 추상 팩토리는 product family의 각 클래스로부터 객체를 생성할 수 있는 인터페이스 제공,
이 인터페이스를 통해 객체를 생성하는 한,
기존에 생성된 product와 다른 변종의 product를 생성할 걱정 X
- 잘 설계된 프로그램에서 하나의 클래스는 하나의 책임만을 가짐
- 한 클래스가 여러 product 종류들을 다룰 때,
팩토리 메서드를 독립적인 팩토리 클래스나 추상 팩토리 구현으로 추출하면 좋음
1. 고유한 product 유형 대 product들의 변종들을 나타내는 매트릭스 생성
2. 모든 product 유형에 대해 추상 product 인터페이스를 선언하고,
모든 concrete product로 하여금 그 인터페이스들을 구현하도록 함
3. 모든 추상 product의 생성 메서드 집합을 가진 추상 팩토리 인터페이스 선언
4. 각 product 변종에 대해 concrete 팩토리 클래스 집합 구현
5. 앱 내부에 configuration이나 environment에 근거해
하나의 concrete 팩토리 클래스를 인스턴스화하는 팩토리 초기화 코드 생성하고,
해당 팩토리 객체를 product를 생성하는 모든 클래스에게 전달
6. 코드에 존재하는 모든 product 생성자들에 대한 직접 호출을 찾아
적절한 팩토리 객체 생성 메서드 호출로 변경
- 팩토리에서 생성되는 product들의 상호 호환 보장
- concrete product들과 클라이언트 코드 사이의 결합도 낮음
- SRP - product 생성 코드를 프로그램의 한 위치로 이동해 유지보수성 향상
- OCP - 기존 클라이언트 코드를 훼손하지 않고 새로운 product 변종 도입 가능
- 패턴과 함께 새로운 인터페이스들과 클래스들이 도입되기 때문에 코드가 필요 이상으로 복잡해질 수 있음
- 많은 디자인은 팩토리 메서드(덜 복잡 & 자식 클래스들을 통해 더 많은 커스터마이징 가능)로 시작해
추상 팩토리, 프로토타입 또는 빌더(더 유연 & 더 복잡) 패턴으로 발전
- 빌더 패턴 - 복잡한 객체를 단계를 거쳐 생성하는 것에 중점,
추상 팩토리 - 연관된 객체들의 family를 생성하는 것에 중점을 둠
- 추상 팩토리 - product 즉시 반환
- 빌더 - product를 얻기 전 추가 단계를 거칠 수 있게 해 줌
- 추상 팩토리 클래스 - 팩토리 메서드들의 집합을 기반으로 하는 경우가 많음
- 추상 팩토리 클래스 - 하위 시스템 객체들이 클라이언트 코드에서 생성되는 방식만을 감추고 싶은 경우
퍼사드 패턴의 대안
- 추상 팩토리 + 브리지 - 브리지가 정의한 추상화가 특정한 구현만 다룰 수 있는 경우,
추상 팩토리가 이 관계를 캡슐화해 클라이언트 코드에서 복잡성을 숨길 수 있음
- 추상 팩토리, 빌더, 프로토타입 패턴 모두 싱글턴 패턴으로 구현 가능
/**
* The Abstract Factory interface declares a set of methods that return
* different abstract products. These products are called a family and are
* related by a high-level theme or concept. Products of one family are usually
* able to collaborate among themselves. A family of products may have several
* variants, but the products of one variant are incompatible with products of
* another.
*/
interface AbstractFactory {
createProductA(): AbstractProductA;
createProductB(): AbstractProductB;
}
/**
* Concrete Factories produce a family of products that belong to a single
* variant. The factory guarantees that resulting products are compatible. Note
* that signatures of the Concrete Factory's methods return an abstract product,
* while inside the method a concrete product is instantiated.
*/
class ConcreteFactory1 implements AbstractFactory {
public createProductA(): AbstractProductA {
return new ConcreteProductA1();
}
public createProductB(): AbstractProductB {
return new ConcreteProductB1();
}
}
/**
* Each Concrete Factory has a corresponding product variant.
*/
class ConcreteFactory2 implements AbstractFactory {
public createProductA(): AbstractProductA {
return new ConcreteProductA2();
}
public createProductB(): AbstractProductB {
return new ConcreteProductB2();
}
}
/**
* Each distinct product of a product family should have a base interface. All
* variants of the product must implement this interface.
*/
interface AbstractProductA {
usefulFunctionA(): string;
}
/**
* These Concrete Products are created by corresponding Concrete Factories.
*/
class ConcreteProductA1 implements AbstractProductA {
public usefulFunctionA(): string {
return 'The result of the product A1.';
}
}
class ConcreteProductA2 implements AbstractProductA {
public usefulFunctionA(): string {
return 'The result of the product A2.';
}
}
/**
* Here's the the base interface of another product. All products can interact
* with each other, but proper interaction is possible only between products of
* the same concrete variant.
*/
interface AbstractProductB {
/**
* Product B is able to do its own thing...
*/
usefulFunctionB(): string;
/**
* ...but it also can collaborate with the ProductA.
*
* The Abstract Factory makes sure that all products it creates are of the
* same variant and thus, compatible.
*/
anotherUsefulFunctionB(collaborator: AbstractProductA): string;
}
/**
* These Concrete Products are created by corresponding Concrete Factories.
*/
class ConcreteProductB1 implements AbstractProductB {
public usefulFunctionB(): string {
return 'The result of the product B1.';
}
/**
* The variant, Product B1, is only able to work correctly with the variant,
* Product A1. Nevertheless, it accepts any instance of AbstractProductA as
* an argument.
*/
public anotherUsefulFunctionB(collaborator: AbstractProductA): string {
const result = collaborator.usefulFunctionA();
return `The result of the B1 collaborating with the (${result})`;
}
}
class ConcreteProductB2 implements AbstractProductB {
public usefulFunctionB(): string {
return 'The result of the product B2.';
}
/**
* The variant, Product B2, is only able to work correctly with the variant,
* Product A2. Nevertheless, it accepts any instance of AbstractProductA as
* an argument.
*/
public anotherUsefulFunctionB(collaborator: AbstractProductA): string {
const result = collaborator.usefulFunctionA();
return `The result of the B2 collaborating with the (${result})`;
}
}
/**
* The client code works with factories and products only through abstract
* types: AbstractFactory and AbstractProduct. This lets you pass any factory or
* product subclass to the client code without breaking it.
*/
function clientCode(factory: AbstractFactory) {
const productA = factory.createProductA();
const productB = factory.createProductB();
console.log(productB.usefulFunctionB());
console.log(productB.anotherUsefulFunctionB(productA));
}
/**
* The client code can work with any concrete factory class.
*/
console.log('Client: Testing client code with the first factory type...');
clientCode(new ConcreteFactory1());
console.log('');
console.log('Client: Testing the same client code with the second factory type...');
clientCode(new ConcreteFactory2());
참고 자료: Refactoring.guru