디자인패턴

팩토리 패턴

2료일 2025. 3. 25. 18:11

객체 생성을 캡슐화하는 생성 패턴중 하나이다. 객체를 직접 생성하는 대신, 팩토리라는 중간 객체를 통해 생성한다. 이렇게 되면 코드의 유연성이 높아지고, 객체 생성 로직을 한 곳에서 관리할 수 있어 유지보수가 쉬워진다. 

팩토리 메서드 패턴

언제쓰지?

1. 단일 제품의 다양한 변형을 생성할 때

2 어떻게 하나의 객체를 생성할 것인가에 초점을 맞춘다.

ex) iOS 앱에서 배송 관리 앱을 만든다고 가정해봅시다. 처음에는 트럭(Truck)만 지원했기 때문에 모든 코드가 Truck클래스에 의존적입니다. 그런데 갑자기 배송 업체에서 "배(Ship)"도 지원해달라는 요청이 들어왔습니다.

  • 기존 코드: let transport = Truck()처럼 직접 생성.
  • 문제: Ship을 추가하려면 클라이언트 코드 전체를 수정해야 하고, 나중에 또 다른 운송 수단(예: 비행기)이 추가되면 계속 반복됩니다. 조건문(if-else)이 난무하게 되죠

그래서 클라이언트는 구체적인 클래스(Truck, ship)을 알필요 없이 추상화된 인터페이스만 사용하는 팩토리 메서드 패턴을 적용해보자.

// Product 인터페이스
protocol Transport {
    func deliver() -> String
}

// Concrete Product
class Truck: Transport {
    func deliver() -> String {
        return "Delivering by land with a truck"
    }
}

class Ship: Transport {
    func deliver() -> String {
        return "Delivering by sea with a ship"
    }
}

// Creator
protocol Logistics {
    func createTransport() -> Transport // 팩토리 메서드
    func planDelivery() -> String
}

extension Logistics {
    func planDelivery() -> String {
        let transport = createTransport()
        return transport.deliver()
    }
}

// Concrete Creator
class RoadLogistics: Logistics {
    func createTransport() -> Transport {
        return Truck()
    }
}

class SeaLogistics: Logistics {
    func createTransport() -> Transport {
        return Ship()
    }
}

// 클라이언트 코드
func clientCode(logistics: Logistics) {
    print(logistics.planDelivery())
}

let road = RoadLogistics()
let sea = SeaLogistics()

clientCode(logistics: road) // 출력: Delivering by land with a truck
clientCode(logistics: sea)  // 출력: Delivering by sea with a ship

Product 인터페이스: TransPort 팩토리 메서드가 생성하는 객체들이 구현해야 하는 공통 인터페이스

Concrete Products(트럭, 배): TransPort인터페이스를 구체적으로 구현한 클래스들

Creator(Logisitcs 프로토콜): 객체 생성을 위한 팩토리 메서드(createTransPort)를 선언하고, 이 메서드를 사용하는 기본 비즈니스 로직 포함

Concrete Creators(RoadLogistics, seaLogistics): Logistics 프로토콜을 구현하여 팩토리 메서드를 오버라이드하고 자신만의 제품 유형을 반환

장점 : 결합도 감소(클라이언트와 구체적인 클래스가 분리), SRP(객체 생성 로직을 한 곳에 모아 관리하기 쉬워진다), OCP( 새로운 운송 수단을 추가하려면 새 서브클래스만 만들면된다)

추상 팩토리 패턴

 

: 구체적인 클래스를 지정하지 않고도 관련된 객체의 집합을 생성해주는 생성 디자인 패턴

관련된 객체들의 집합을 생성하는데 중점을 둔다. 

- 어떻게 호환되는 객체 군을 생성할 것인가에 초점

ex) 가구 쇼핑앱을 만든다고 생각해보자. 앱에서 의자, 소파, 테이블을 판매하는데 이 가구들은 스타일에 따라 다르게 제공된다. 고객은 같은 스타일로 맞춘 가구 세트를 원한다. 이때 추상 팩토리 패턴을 사용하면 스타일별로 일관된 가구 집합을 쉽게 생성할 수 있어진다.

구성요소

1. Abstract Product: 관련된 제품군의 인터페이스(Chair, Sofa)

2. Concrete Product: 각변형에 따른 구체적인 구현(ModernChair, VictorianChair)

3. Abstract Factory: 모든 제품을 생성하는 메서드 집합을 정의한 인터페이스(FurnitureFactory)

4. Concrete Factory: 특정 변형에 맞는 제품을 생성하는 구현체(ModernFurnitureFactory)

5. Client: 추상팩토리와 제품 인터페이스를 사용하는 코드

// Abstract Product
protocol Chair {
    func sitOn() -> String
}

protocol Sofa {
    func lieOn() -> String
}

// Concrete Product
class ModernChair: Chair {
    func sitOn() -> String {
        return "Sitting on a sleek modern chair"
    }
}

class ModernSofa: Sofa {
    func lieOn() -> String {
        return "Lying on a comfy modern sofa"
    }
}

class VictorianChair: Chair {
    func sitOn() -> String {
        return "Sitting on an ornate Victorian chair"
    }
}

class VictorianSofa: Sofa {
    func lieOn() -> String {
        return "Lying on a luxurious Victorian sofa"
    }
}

// Abstract Factory
protocol FurnitureFactory {
    func createChair() -> Chair
    func createSofa() -> Sofa
}

// Concrete Factory
class ModernFurnitureFactory: FurnitureFactory {
    func createChair() -> Chair {
        return ModernChair()
    }
    
    func createSofa() -> Sofa {
        return ModernSofa()
    }
}

class VictorianFurnitureFactory: FurnitureFactory {
    func createChair() -> Chair {
        return VictorianChair()
    }
    
    func createSofa() -> Sofa {
        return VictorianSofa()
    }
}

// Client 코드
class FurnitureShop {
    private let factory: FurnitureFactory
    
    init(factory: FurnitureFactory) {
        self.factory = factory
    }
    
    func setupRoom() -> String {
        let chair = factory.createChair()
        let sofa = factory.createSofa()
        return "\(chair.sitOn()) and \(sofa.lieOn())"
    }
}

// 사용 예시
let modernShop = FurnitureShop(factory: ModernFurnitureFactory())
print(modernShop.setupRoom())
// 출력: Sitting on a sleek modern chair and Lying on a comfy modern sofa

let victorianShop = FurnitureShop(factory: VictorianFurnitureFactory())
print(victorianShop.setupRoom())
// 출력: Sitting on an ornate Victorian chair and Lying on a luxurious Victorian sofa

 

팩토리메서드와 추상팩토리 차이

팩토리 메서드가 단일 객체 생성에 초점을 맞췄다면, 추상팩토리는 객체 집합을 다룬다. 

팩토리 메서드는 상속으로 객체를 만들지만 추상팩토리는 Composition(객체 구성)으로 만든다.

팩초리 메서드 패턴으로 객체를 생성할때는 클래스를 확장하고 팩토리 메서드를 오버라이드 해야겟군..

팩토리 메서드 패턴을 사용하는 이유? 서브클래스로 객체를 만들어 클라이언트는 구상형식을 서브클래스에서 처리하기에 자신이 사용할 추상형식만 알면된다 -> 클라이언트와 구상 형식을 분리한다.

추상팩토리 패턴은 제품군을 만드는 추상형식을 제공한다. 팩토리를 사용하고 싶으면 일단 인스턴스로 만든 다음 추상형식을 써서 만든 코드에 전달. 일련의 연관된 제품을 하나로 묶을때 용이

protocol Pizza {
    func prepare()
}

class CheesePizza: Pizza {
    func prepare() { print("Preparing Cheese Pizza") }
}

class PepperoniPizza: Pizza {
    func prepare() { print("Preparing Pepperoni Pizza") }
}

class PizzaStore {
    func orderPizza(type: String) -> Pizza? {
        let pizza = createPizza(type: type)
        pizza?.prepare()
        return pizza
    }
    
    // 팩토리 메서드: 서브클래스가 구현
    func createPizza(type: String) -> Pizza? {
        fatalError("Subclasses must override createPizza")
    }
}

class NYPizzaStore: PizzaStore {
    override func createPizza(type: String) -> Pizza? {
        switch type {
        case "cheese": return CheesePizza()
        case "pepperoni": return PepperoniPizza()
        default: return nil
        }
    }
}

// 사용
let nyStore = NYPizzaStore()
nyStore.orderPizza(type: "cheese") // 출력: Preparing Cheese Pizza
// 재료
protocol Dough { func description() -> String }
protocol Sauce { func description() -> String }
protocol Cheese { func description() -> String }

class NYDough: Dough { func description() -> String { "Thin Crust Dough" } }
class NYSauce: Sauce { func description() -> String { "Marinara Sauce" } }
class NYCheese: Cheese { func description() -> String { "Mozzarella Cheese" } }

class ChicagoDough: Dough { func description() -> String { "Thick Crust Dough" } }
class ChicagoSauce: Sauce { func description() -> String { "Tomato Sauce" } }
class ChicagoCheese: Cheese { func description() -> String { "Parmesan Cheese" } }

// 추상 팩토리
protocol PizzaIngredientFactory {
    func createDough() -> Dough
    func createSauce() -> Sauce
    func createCheese() -> Cheese
}

class NYPizzaIngredientFactory: PizzaIngredientFactory {
    func createDough() -> Dough { NYDough() }
    func createSauce() -> Sauce { NYSauce() }
    func createCheese() -> Cheese { NYCheese() }
}

class ChicagoPizzaIngredientFactory: PizzaIngredientFactory {
    func createDough() -> Dough { ChicagoDough() }
    func createSauce() -> Sauce { ChicagoSauce() }
    func createCheese() -> Cheese { ChicagoCheese() }
}

// 피자
class Pizza {
    var dough: Dough?
    var sauce: Sauce?
    var cheese: Cheese?
    
    func prepare(factory: PizzaIngredientFactory) {
        dough = factory.createDough()
        sauce = factory.createSauce()
        cheese = factory.createCheese()
        print("Preparing pizza with: \(dough!.description()), \(sauce!.description()), \(cheese!.description())")
    }
}

// 사용
let nyFactory = NYPizzaIngredientFactory()
let pizza = Pizza()
pizza.prepare(factory: nyFactory) // 출력: Preparing pizza with: Thin Crust Dough, Marinara Sauce, Mozzarella Cheese

참고자료

https://refactoring.guru/design-patterns/factory-method

 

Factory Method

/ Design Patterns / Creational Patterns Factory Method Also known as: Virtual Constructor Intent Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objec

refactoring.guru

 

https://refactoring.guru/design-patterns/factory-method