생성 패턴 - 추상 팩토리 패턴 (Abstract Factory Pattern)

French Marigold·2024년 5월 7일
1

디자인패턴

목록 보기
9/10

정의

  • 추상 팩토리 패턴 (Factory Method Pattern)이란 연관성이 있는 객체끼리 묶어 이를 추상화하고, 그 팩토리 객체 내에서 집합으로 묶은 객체 군을 다양한 형태로 구현화하는 생성 패턴이다.
  • 예를 들어 Button, TextField, Switch라는 UI 객체가 있다고 가정해보자. 이 객체들을 우리는 애플 OS에서도 사용하고 싶고, Window OS에서도 사용하고 싶으며, Linux OS에서도 사용하고 싶을 경우, 이러한 복잡한 구조를 관리하기 용이하게 하기 위해 추상 팩토리 패턴이 사용된다고 보면 된다.

추상 팩토리 패턴 (Abstract Factory Pattern) 의 구조

  • AbstractFactory ⇒
    • 최상위 공장 프로토콜.
    • 다양한 형태의 제품을 생성해내는 여러 메소드들을 추상화한다.
  • ConcreteFactory ⇒
    • 프로토콜이었던 AbstractFactory(추상 공장)를 실제 구체적인 객체로 옮기는 영역.
    • “최상위 공장에 존재하는 여러 객체를 “여러 형태의 제품군 (애플 OS, Window OS, Rinux OS)”으로 나누어 구체화”시킬 수 있다.
  • AbstractProduct ⇒
    • Abstract Factory에 들어갈 하나 하나의 객체 프로토콜 타입.
    • Abstract Product는 Abstract Factory 내부의 메소드의 리턴 타입이 된다. ⭐️
  • ConcreteProduct
    • 프로토콜이었던 AbstractProduct(추상화 객체)를 실제 구체적인 객체로 옮기는 영역.

추상 팩토리 패턴과 팩토리 메소드 패턴의 공통점과 차이점

  • 공통점
    • 객체 생성 과정을 “팩토리 객체를 통해 추상화”한다.
    • Client 단에서는 직접적인 객체 타입이 아닌 추상적인 객체 타입 인스턴스를 팩토리 객체로부터 받아오므로 의존성을 떨어뜨림과 동시에 결합도를 크게 줄일 수 있다.
  • 차이점
    • 추상 팩토리 패턴
      • 한 팩토리 객체(프로토콜) 내부에 서로 연관된 “여러 종류의 객체 생성”을 지원한다.
      • 모니터, 마우스, 키보드를 묶은 전자 제품군이 있는데 그 제품군을 또 애플 제품군이냐, 삼성 제품군이냐, 로지텍 제품군이냐로 나눌 경우, 이 복잡한 구조를 관리하기 용이하게 만드는 것이 주 목적.
    • 팩토리 메소드 패턴
      • 한 팩토리 객체(프로토콜) 내부에는 “하나의 객체 생성”만 지원한다.
      • 팩토리 객체 내에서 객체 생성 인스턴스를 프로토콜 타입으로 지원하여 Client와 실제 객체와의 결합도를 낮추는 것이 주 목적.

Swift로 추상 팩토리 패턴 적용 과정 예시

  1. 신용카드, 페이팔 등으로 결제 처리 방법이 다를 경우를 예시로 추상 팩토리 패턴을 적용해 볼 것이다. 우선 객체 내부의 로직을 표현하기 위한 Abstract Product 프로토콜을 생성한다.
// Abstract Product

// 결제 처리 내부 로직을 담은 프로토콜 메소드가 존재함.
protocol PaymentProcessor {
    func processPayment()
}

// 어떠한 조건을 충족할 경우에만 결제를 처리하도록 구분해주는 프로토콜 메소드가 존재함.
protocol PaymentValidator {
    func validatePaymentDetails() -> Bool
}
  1. 그 후, Abstract Product 프로토콜을 채택한 구체적인 객체들을 생성하여 내부를 구현한다.
// Concrete Product
class CreditCardProcessor: PaymentProcessor {
    func processPayment() {
        // 신용 카드를 이용할 때의 결제 처리 로직을 "구체적으로 구현"
    }
}

class CreditCardValidator: PaymentValidator {
    func validatePaymentDetails() -> Bool {
        // 어떠한 조건을 충족할 경우 true를 반환하게 하고
        // 조건을 불충족할 경우 false를 반환하게 하는 로직을 "구체적으로 구현."
        // 예를 들면 비밀번호를 올바르게 입력할 경우 true를 반환,
        // 비밀번호를 올바르게 입력하지 않았으면 false를 반환.
        return true // 여기서는 편의상 true를 반환
    }
}

class PayPalProcessor: PaymentProcessor {
    func processPayment() {
        // 페이팔을 이용할 때의 결제 처리 로직을 "구체적으로 구현"
    }
}

class PayPalValidator: PaymentValidator {
    func validatePaymentDetails() -> Bool {
        // 어떠한 조건을 충족할 경우 true를 반환하게 하고
        // 조건을 불충족할 경우 false를 반환하게 하는 로직을 "구체적으로 구현."
        // 예를 들면 비밀번호를 올바르게 입력할 경우 true를 반환,
        // 비밀번호를 올바르게 입력하지 않았으면 false를 반환.
        return true // 간단한 예시로 항상 true를 반환
    }
}
  1. Abstract Factory는 Abstract Product를 리턴 타입으로 가지는 메소드를 소유하고 있다. 추상 팩토리 패턴에서 Abstract Factory는 Abstract Product를 생성해내는 메소드를 “여러 개 보유할 수 있다.“
// Abstract Factory
protocol PaymentFactory {
    func createPaymentProcessor() -> PaymentProcessor
    func createPaymentValidator() -> PaymentValidator
}
  1. Abstract Factory 프로토콜을 채택하여 구체적인 공장 객체 (Concrete Factory)를 여러 종류로 만들어낸다. (CreditCardFactory, PaypalFactory etc.)
// Concrete Factory
class CreditCardFactory: PaymentFactory {
    func createPaymentProcessor() -> PaymentProcessor {
        return CreditCardProcessor()
    }
    
    func createPaymentValidator() -> PaymentValidator {
        return CreditCardValidator()
    }
}

class PayPalFactory: PaymentFactory {
    func createPaymentProcessor() -> PaymentProcessor {
        return PayPalProcessor()
    }
    
    func createPaymentValidator() -> PaymentValidator {
        return PayPalValidator()
    }
}
  1. Client에서는 Abstract Factory 프로토콜을 타입으로 하는 상수를 선언하고 생성자로 초기화한다. 이 상수에 Abstract Factory 프로토콜을 타입으로 갖는 모든 객체가 할당될 수 있다.
// Payment System
class PaymentSystem {
    private let factory: PaymentFactory
    
    init(factory: PaymentFactory) {
        self.factory = factory
    }
    
    func processPayment() {
        let validator = factory.createPaymentValidator()
        
        // Validator를 통해 결제 시스템의 특정 조건을 충족하였을 경우
        // 예를 들면 비밀번호를 올바르게 입력했을 경우 true,
        // 올바르게 입력하지 않았을 경우 false
        if validator.validatePaymentDetails() {
            
            // 신용카드 혹은 페이팔 인스턴스를 생성 후 (추후 인스턴스는 외부에서 주입될 예정)
            let processor = factory.createPaymentProcessor()
            
            // 각 인스턴스의 결제 처리 로직에 따라 로직을 구현
            processor.processPayment()
        } else {
            // Validator를 통해 결제 시스템의 조건을 충족하지 못했을 경우 
            print("결제 처리가 실패하였습니다")
        }
    }
}

// Client
class Client {
    // 신용카드로 지불할 경우
    let creditCardPaymentSystem = PaymentSystem(factory: CreditCardFactory())
    // PayPal로 지불하는 경우
    let payPalPaymentSystem = PaymentSystem(factory: PayPalFactory())

    
    // 신용카드의 결제 처리 시스템을 실행
    func creditCardProcessPayment() {
        creditCardPaymentSystem.processPayment()
    }
    
    // 페이팔의 결제 처리 시스템을 실행
    func payPalProcessPayment() {
        payPalPaymentSystem.processPayment()
    }
    
}

패턴 사용 시기

  • 여러 가지 객체들을 여러 종류군으로 나누는 복잡한 구조를 편리하게 관리하고자 할 때 사용된다.
    • Button, TextField, Switch라는 UI 객체가 있을 경우, 이 객체들을 우리는 애플 OS에서도 사용하고 싶고, Window OS에서도 사용하고 싶으며, Linux OS에서도 사용하고 싶을 경우. (복잡한 구조를 관리)

패턴의 장점

  • SRP ⇒ 여러 가지 객체들이 모두 자신의 책임에만 충실하다. 즉, 책임이 단일하다.
  • OCP ⇒ Abstract Factory 프로토콜을 통해 기존 코드를 수정하지 않고도 새로운 유형의 Concrete Factory를 계속해서 생성할 수 있으므로 OCP를 만족한다.
  • 여러 객체가 다양한 형태로 나뉘어져 있는 복잡한 구조를 쉽게 관리할 수 있다.

패턴의 단점

  • 새로운 제품군이 추가될 때마다 팩토리 객체들을 추가하여 내부를 모두 구현해주어야 하기 때문에 코드의 복잡성이 증가한다. (팩토리 관련 패턴의 공통적인 문제점이다.)
  • 새로운 종류의 객체를 추가하면 (Abstract Factory 내부에 새로운 메소드를 추가) 관련 Concrete Factory 객체를 모두 수정해주어야 하기 때문에 새로운 종류의 객체를 추가하기는 까다롭다.

참고 문헌

profile
꽃말 == 반드시 오고야 말 행복

1개의 댓글

comment-user-thumbnail
2024년 5월 11일

최근 개인적으로 추상 팩토리에 대한 내용을 읽으며,
이전에 읽었던 마리골드님의 메서드 팩토리와는 무슨 차이인 지 고민하던 차인데 반가운 포스팅입니다!
제가 추상 팩토리에 대한 내용을 접한 것은 '클린 아키텍처' 책의 DIP(의존성 역전 원칙)에 대해 다루는 글 속에서 입니다.

변동성이 큰 구체적인 객체는 특별히 주의해서 생성해야 한다. (중략) 자바 등 대다수의 객체 지향 언어에서 이처럼 바람직하지 못한 의존성을 처리할 때 추상 팩토리를 사용하곤 한다.

포스팅을 읽으며 제가 가진 추상 팩토리 개념을 점검하고, 이전 메서드 팩토리와의 차이점도 명확히 할 수 있었습니다.
감사히 잘 읽었습니다!

답글 달기