애플사이다의 iOS 개발 일지

[디자인 패턴] Factory Method - 비슷한 종류의 타입을 찍어낼 때 본문

프로그래밍 철학

[디자인 패턴] Factory Method - 비슷한 종류의 타입을 찍어낼 때

Applecider 2023. 1. 14. 18:29

GoF 디자인 패턴 중 하나인 Factory Method 패턴을 정리했다.

- Ref : 도서 <Dive into Design Patterns>, Alexander Shvets 저


문제 상황

  • 물류관리 앱을 개발하는 과정에서 먼저 Truck (트럭) 운송 처리 로직을 만들었는데, 얼마 뒤 Ship (선박) 등 추가적인 운송수단 처리가 필요하게 됐다.
  • 대부분의 코드가 기존의 Truck 클래스에 결합되어 있어 코드 재사용성이 떨어지는 문제가 있는 상황이다.

패턴 설명

  • 요약 : 부모클래스에서 객체 생성 인터페이스를 제공하며, 동시에 자식클래스가 객체의 유형을 변경하여 생성 가능하도록 하는 패턴이다.
  • Product, Creator 역할이 필요하다.

Factory Method 구조도

1. Product

  • 일반적인 객체 생성 코드를 부모클래스의 팩토리 메서드 내부에 배치한다.
    팩토리 메서드는 생성한 객체를 반환하며, 이 객체를 Product (제품)라고 부른다.
  • 현재 문제상황에서 Product는 Transport (Truck, Ship 등)이다.
  • 여러 종류의 Product는 공통적인 프로토콜을 채택해야 한다.
  • 클라이언트 (팩토리 메서드를 사용하는 코드)는 Product 간의 차이를 모른다.

2. Creator

  • 위에서 설명한 부모클래스 역할을 Creator 클래스가 한다.
  • 현재 문제상황에서 Creator는 Logistics (RoadLogistics, SeaLogistics 등)이다.
  • 이제 자식클래스에서 팩토리 메서드를 override하여 반환하는 Product 타입을 변경할 수 있다.
  • 네이밍에서 예상되는 것과 달리, Creator의 책임은 제품을 생성하는 게 아니라 제품과 관련된 핵심 비즈니스 로직을 처리하는 것이다. 팩토리 메서드는 이러한 비즈니스 로직을 Concrete Product 클래스로부터 분리 (디커플링)하는 데 도움을 준다.

예시 코드

부모클래스 Logistics는 팩토리 메서드에서 Truck (default transport 개념)을 생성하도록 했고,
자식클래스인 Road/SeaLogistics는 팩토리 메서드를 override하여 각각 Truck/Ship을 자체적으로 생성한다.

// MARK: - 1️⃣ Product
protocol Transport {
    func deliver()
}
class Truck: Transport {  // Concrete Product-1
    func deliver() {
        print("Truck - delivery started")
    }
}
class Ship: Transport {  // Concrete Product-2
    func deliver() {
        print("Ship - delivery started")
    }
}

// MARK: - 2️⃣ Creator
class Logistics {
    func createTransport() -> Transport {
        let defaultTransport = Truck()
        print("Truck - created")
        return defaultTransport
    }
    
    func service() {
        let transport: Transport = createTransport()
        print("Packages on board")
        transport.deliver()
    }
}
class RoadLogistics: Logistics {  // Concrete Creator-1
    override func createTransport() -> Transport {
        print("Truck - created")
        return Truck()
    }
}
class SeaLogistics: Logistics {  // Concrete Creator-2
    override func createTransport() -> Transport {
        print("Ship - created")
        return Ship()
    }
}

// MARK: - ✅ Client
let roadLogistics = RoadLogistics()
roadLogistics.service()
//Truck - created
//Packages on board
//Truck - delivery started

let seaLogistics = SeaLogistics()
seaLogistics.service()
//Ship - created
//Packages on board
//Ship - delivery started

활용안을 상상해보면 아래 형태일듯

if destinationType == .sameLand {
    let roadLogistics = RoadLogistics()
    roadLogistics.service()
} else {
    let seaLogistics = SeaLogistics()
    seaLogistics.service()
}

장점

  • 데이터베이스 연결, 파일시스템, 네트워크 등 대규모 객체를 처리하는 상황에서 시스템 리소스를 절약할 수 있다.
  • Creator와 Concrete Product의 결합도를 낮춘다.
  • 제품 초기화 기능을 한 객체에서 전담하므로 단일책임 원칙을 준수한다.
  • 클라이언트 코드에 영향을 주지 않은 채로 새로운 Product를 도입할 수 있으므로 개방폐쇄 원칙을 준수한다.

단점

  • 새로운 자식클래스를 생성해야 하므로 코드 복잡도가 높아진다.
    따라서 Creator 클래스의 기존 계층구도에 패턴을 도입하는 것이 좋다.

- Reference

 

🍎 포스트가 도움이 되었다면, 공감🤍 / 구독🍹 / 공유🔗 / 댓글✏️ 로 응원해주세요. 감사합니다.

 

Comments