추상 팩토리는 관련 객체들의 구상 클래스들을 지정하지 않고도 관련 객체들의 모음을 생성할 수 있도록 하는 생성패턴이다.
가구 판매장을 위한 프로그램을 만들고 있다. 현재 생성해야 되는 가구와 제춤군을 다음과 같다.
관련 제품들로 형성된 패밀리(제품군): Chair(의자) + Sofa(소파) + CoffeeTable(커피 테이블)
해당 제품군의 디자인: Modern(현대식), Victorian(빅토리안), ArtDeco(아르데코 양식)
이제 새로운 개별 가구 객체를 생성했을 때, 이 객체들이 기존의 같은 제품군 내에 있는 다른 가구 객체들과 스타일(디자인)을 가지도록 할 방법이 필요하다.
또, 가구 공급업체들은 카탈로그를 매우 자주 변경하기 때문에, 그들은 새로운 제품 또는 제품군(패밀리)을 추가할 때마다 기존 코드를 변경해야 하는 번거로움을 피하고 싶을 것이다.
추상 팩터리 패턴의 첫 번째 방안은 각 제품 제품군에 해당하는 개별적인 인터페이스를 명시적으로 선언하는 것이다. (예: 의자, 소파 또는 커피 테이블).
그 다음, 제품의 모든 변형이 위 인터페이스를 따르도록 한다. 예를 들어, 모든 의자의 변형들은 Chair(의자) 인터페이스를 구현한다; 모든 커피 테이블 변형들은 CoffeeTable(커피 테이블) 인터페이스를 구현한다, 등의 규칙을 명시합니다.
추상 공장 패턴의 다음 단계는 추상 팩토리 패턴을 선언하는 것입니다. 추상 공장 패턴은 제품 패밀리 내의 모든 개별 제품들의 생성 메서드들이 목록화되어있는 인터페이스입니다. (예: createChair(의자 생성), createSofa(소파 생성), createCoffeeTable(커피 테이블 생성) 등).
다음은 제품 변형을 다룰 차례입니다. 제품 패밀리의 각 변형에 대해 AbstractFactory 추상 팩토리 인터페이스를 기반으로 별도의 팩토리 클래스를 생성합니다. 팩토리는 특정 종류의 제품을 반환하는 클래스입니다. 예를 들어 ModernFurnitureFactory(현대식 가구 팩토리)에서는 다음 객체들만 생성할 수 있습니다: ModernChair(현대식 의자), ModernSofa(현대식 소파) 및 ModernCoffeeTable(현대식 커피 테이블).
클라이언트 코드는 자신에 해당하는 추상 인터페이스를 통해 팩토리들과 제품들 모두와 함께 작동해야 합니다. 그래야 클라이언트 코드에 넘기는 팩토리의 종류와 제품 변형들을 클라이언트 코드를 손상하지 않으며 자유자재로 변경할 수 있습니다.
클라이언트가 팩토리에 의자를 주문했다고 가정해 봅시다. 클라이언트는 팩토리의 클래스들을 알 필요가 없으며, 팩토리가 어떤 변형의 의자를 생성할지 전혀 신경을 쓰지 않습니다. 클라이언트는 추상 Chair(의자) 인터페이스를 사용하여, 현대식 의자이든 빅토리아식 의자이든 종류에 상관없이 모든 의자를 항상 동일한 방식으로 주문하며, 그가 의자에 대해 아는 유일한 사실은 제품이 sitOn(앉을 수 있다) 메서드를 구현한다는 것뿐입니다. 그러나, 생성된 의자의 변형은 항상 같은 팩토리 객체에서 생성된 소파 또는 커피 테이블의 변형과 같을 것입니다.
여기에서 명확히 짚고 넘어가야 할 점이 있습니다. 클라이언트가 추상 인터페이스에만 노출된다면 실제 팩토리 객체를 생성하는 것은 무엇일까요? 일반적으로 프로그램은 초기화 단계에서 구상 팩토리 객체를 생성합니다. 그 직전에 프로그램은 환경 또는 구성 설정에 따라 팩토리 유형을 선택해야 합니다.
클라이언트가 직접 ModernChair
, VictorianChair
, ModernSofa
, VictorianSofa
와 같은 구상 클래스를 생성하고 있다. 이는 클라이언트 코드가 특정 의자와 소파 구현에 의존함을 의미합니다. 때문에 만약 새로운 종류의 의자나 소파가 추가되거나 기존의 의자나 소파 구현이 변경되면 클라이언트 코드도 수정해야 합니다. 이는 유지보수가 어렵고 확장성이 낮은 코드를 만들게 됩니다.
// 의자 클래스들
class ModernChair {
void sitOn() {
System.out.println("Sitting on a modern chair.");
}
}
class VictorianChair {
void sitOn() {
System.out.println("Sitting on a Victorian chair.");
}
}
// 소파 클래스들
class ModernSofa {
void lieOn() {
System.out.println("Lying on a modern sofa.");
}
}
class VictorianSofa {
void lieOn() {
System.out.println("Lying on a Victorian sofa.");
}
}
// 클라이언트 코드
public class Client {
public void orderModernChair() {
ModernChair chair = new ModernChair();
chair.sitOn();
}
public void orderVictorianChair() {
VictorianChair chair = new VictorianChair();
chair.sitOn();
}
public void orderModernSofa() {
ModernSofa sofa = new ModernSofa();
sofa.lieOn();
}
public void orderVictorianSofa() {
VictorianSofa sofa = new VictorianSofa();
sofa.lieOn();
}
public static void main(String[] args) {
Client client = new Client();
client.orderModernChair();
client.orderVictorianChair();
client.orderModernSofa();
client.orderVictorianSofa();
}
}
추상 팩터리 패턴을 적용하여 각각의 가구 팩토리는 관련된 의자와 소파를 생성하며, 클라이언트는 필요한 가구 팩토리를 선택하여 사용할 수 있습니다. 클라이언트는 추상 인터페이스를 통해 팩토리에서 생성된 제품들을 사용하므로, 구상 클래스에 대한 의존성이 감소하고 유연성과 확장성이 향상됩니다.
// 추상 의자 인터페이스
interface Chair {
void sitOn();
}
// 추상 소파 인터페이스
interface Sofa {
void lieOn();
}
// 현대식 의자 구현
class ModernChair implements Chair {
@Override
public void sitOn() {
System.out.println("Sitting on a modern chair.");
}
}
// 빅토리아식 의자 구현
class VictorianChair implements Chair {
@Override
public void sitOn() {
System.out.println("Sitting on a Victorian chair.");
}
}
// 현대식 소파 구현
class ModernSofa implements Sofa {
@Override
public void lieOn() {
System.out.println("Lying on a modern sofa.");
}
}
// 빅토리아식 소파 구현
class VictorianSofa implements Sofa {
@Override
public void lieOn() {
System.out.println("Lying on a Victorian sofa.");
}
}
// 추상 가구 팩토리 인터페이스
interface FurnitureFactory {
Chair createChair();
Sofa createSofa();
}
// 현대식 가구 팩토리 구현
class ModernFurnitureFactory implements FurnitureFactory {
@Override
public Chair createChair() {
return new ModernChair();
}
@Override
public Sofa createSofa() {
return new ModernSofa();
}
}
// 빅토리아식 가구 팩토리 구현
class VictorianFurnitureFactory implements FurnitureFactory {
@Override
public Chair createChair() {
return new VictorianChair();
}
@Override
public Sofa createSofa() {
return new VictorianSofa();
}
}
// 클라이언트 코드
public class Client {
private FurnitureFactory factory;
public Client(FurnitureFactory factory) {
this.factory = factory;
}
public void orderChairAndSofa() {
Chair chair = factory.createChair();
Sofa sofa = factory.createSofa();
chair.sitOn();
sofa.lieOn();
}
public static void main(String[] args) {
FurnitureFactory modernFactory = new ModernFurnitureFactory();
Client modernClient = new Client(modernFactory);
modernClient.orderChairAndSofa();
FurnitureFactory victorianFactory = new VictorianFurnitureFactory();
Client victorianClient = new Client(victorianFactory);
victorianClient.orderChairAndSofa();
}
}
https://refactoring.guru/ko/design-patterns/abstract-factory