Design Pattern

[Swift] 디자인 패턴 | 팩토리 메서드 패턴 (Factory Method Patter)_ 생성패턴

iOSDEv 2024. 2. 2. 17:23
생성패턴이란?
객체의 생성에 관련된 패턴으로 객체의 생성절차를 추상화하는 패턴
객체를 생성 합성하는 방법이나객체의 표현방법과 시스템을 분리
객체의 생성과 조합을 캡슐화해 특정 객체가 생성되거나 변경되어도 프로그램 구조에 영향을 크게 받지 않아서 유연성을 제공하게된다

 

팩토리 메서드 패턴

c#에서 게임을 만들 때 주구장창 썼었던 팩토리 메서드 패턴에 대해서 Swift언어로 다뤄보고자 한다

게임을 만들 때 몬스터 생성이나 건물 생성 부분에서 많이 사용했었다

이유는 건물도 목장 집 병원등 다양하지만 우선 지어지고나서의 모습 / 지어지는 단계의 모습 등 공통된것들이 있고

몬스터 같은 경우에도 이동/공격/죽음 등 이러한 액션들이 있다 

 

이러한 작업들을 여러 곳에서 만들 때마다 실수하지 않을 수 있다

 

이렇게 생성할 때 몬스터나 건물이 생성될 시간 

👇🏻

몬스터나 건물을 전전역변수에 등록

👇🏻

몬스터 생성이나 건물이 다 지어졌을 때 완료되었다고 알려줌

👇🏻

몬스터나 건물의 화면에 출력

 

만약 신규 건물이 생기거나 신규 몬스터를 추가할 때

간단하게 프로토콜(인터페이스)에 구현하는 클래스를 만들고 

해당 건물이나 몬스터에 생성가는 부분만 추가해주면 나머지 수정할 필요가 없게 되는 매직..

을 많이 이용했었다

(상위 객체를 사용하기 때문에 신규 객체가 추가되더라도 소스의 대규모 변경을 안해도 됨)

 

Swift는 어떨 때 사용할까?

 

기존 기본 기능에서 추가 기능을 요하면 그에 맞게 추가해주는 패턴이라고 생각하면 편할것 같다

(다시 작성하는게 아닌 그냥 그 상태에서 추가만!)

 

예를 들어 물류 관리 앱을 개발하고 있는 데 첫 번째 버전은 트럭 운송만 처리할 수 있다가

얼마 후에 해상 물류가 추가되어 물류 기능을 앱애 추가해야하는 경우

또 다른 예시로는 공장의 생산라인을 변경하는게 아닌 기본이 되는 생산라인을 설치해두고 요청이 들어오면 요청에 맞는 제품을 생산

(어떠한 요청이 들어올지 몰라~ 하지만 해결하냐 사직서를 내냐 그것이 문제로다..)

 

위의

물류관리앱에 대하여 그림으로 나타내자면 이렇게 나타낼 수 있다

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

 

예시1

  • AppleFactory(Creator) : Factory에 기본역할을 정의하는 인터페이스이다 ( product 반환해야함 ⭐️)
  • IPadFactory, IPhoneFactory : Creator를 채택하고 있고 Product에 구체적으로 기능을 구현하고 있다
  • Product: ConcreteProduct(IPad, IPhone)가 해야할 동작들을 선언만 하는 인터페이스이다
  • Concrete Product: Product를 채택하고 그에 맞게 만든 실제 객체를 뜻한다

 

 

 

예시2

 

// Creator 프로토콜은 Product 클래스의 새로운 객체를 반환하는 팩토리 메서드를 선언합니다.
protocol Creator {

    // Creator는 팩토리 메서드의 기본 구현을 제공할 수 있습니다.
    func factoryMethod() -> Product

    // Creator의 주된 책임은 제품을 생성하는 것이 아닙니다. 대개 Product 객체에 의존하는 핵심 비즈니스 로직을 포함합니다.
    // 서브클래스는 팩토리 메서드를 재정의하고 다른 유형의 제품을 반환함으로써 비즈니스 로직을 간접적으로 변경할 수 있습니다.
    func someOperation() -> String
}

// 이 익스텐션은 Creator의 기본 동작을 구현합니다. 이 동작은 서브클래스에서 재정의될 수 있습니다.
extension Creator {

    func someOperation() -> String {
        // 팩토리 메서드를 호출하여 Product 객체를 생성합니다.
        let product = factoryMethod()

        // 이제 제품을 사용합니다.
        return "Creator: 동일한 생성자 코드가 방금 " + product.operation() + "와 함께 작동했습니다."
    }
}

// Concrete Creators는 팩토리 메서드를 재정의하여 생성되는 제품의 유형을 변경합니다.
class ConcreteCreator1: Creator {

    // 메서드의 서명은 여전히 추상 제품 유형을 사용하지만, 실제로는 구체적인 제품이 반환됩니다.
    public func factoryMethod() -> Product {
        return ConcreteProduct1()
    }
}

class ConcreteCreator2: Creator {

    public func factoryMethod() -> Product {
        return ConcreteProduct2()
    }
}

// Product 프로토콜은 모든 구체적인 제품이 구현해야 할 연산을 선언합니다.
protocol Product {

    func operation() -> String
}

// Concrete Products는 Product 프로토콜의 다양한 구현을 제공합니다.
class ConcreteProduct1: Product {

    func operation() -> String {
        return "{ConcreteProduct1의 결과}"
    }
}

class ConcreteProduct2: Product {

    func operation() -> String {
        return "{ConcreteProduct2의 결과}"
    }
}

// Client 코드는 구체적인 생성자의 인스턴스와 함께 작동하지만, 기본 프로토콜을 통해 작동합니다.
class Client {
    // ...
    static func someClientCode(creator: Creator) {
        print("Client: 생성자의 클래스를 알지 못하지만 여전히 작동합니다.\n"
            + creator.someOperation())
    }
    // ...
}

// 이제 모두가 어떻게 함께 작동하는지 살펴보겠습니다.
class FactoryMethodConceptual {

    func demonstrateFactoryMethodConcept() {
        // 애플리케이션은 구성 또는 환경에 따라 생성자 유형을 선택합니다.
        print("App: ConcreteCreator1로 실행됨.")
        Client.someClientCode(creator: ConcreteCreator1())

        print("\nApp: ConcreteCreator2로 실행됨.")
        Client.someClientCode(creator: ConcreteCreator2())
    }
}

// 팩토리 메서드 컨셉을 시연하는 테스트 클래스
let demo = FactoryMethodConceptual()
demo.demonstrateFactoryMethodConcept()

 

 

'

 

  •  장점
    • 프로토콜 기본 기능으로 정의를 하였기 때문에 코드를 변경하지 않고(개방/폐쇄원칙으로 기존 코드를 훼손하지 않음) 새로운 하위클래스에 추가가 가능하기 때문에 유연하고 확장성이 높음 
      • 그래서 단일 책임 원칙으로 생성 코드를 프로그램 위치로 이동해서 코드를 더 쉽게 유지관리할 수 있다
    • 코드에 수정사항이 생기더라도 팩토리 메서드만 수정하면 되기 때문에 수정에 용이함
    •  크리에이터와 구상 제품들이 결합되지 않을 수 있음
  • 단점
    • 새로운 제품이나 기능이 추가될 때마다 새롭게 하위 클래스를 정의해줘야 하기 때문에 불필요하게 많은 클래스가 정의될 수도 있어 복잡해지는 경우가 있음
    • 중첩되어 사용되면 복잡해질 수 있음
    • 구체적인 제품과 크리에이터 간 강한결합이 생길 수 있음 

 

위의 예시가 너무 잘 나와있기에 예시를 나름대로 만들어보았다

//내가 만들어보기
//출발 배송지 부터 배송중인지 배송을 할 수 없는 상태인지 배송을 완료되었는지 걸리는 시간에 대해서 알아보자

protocol Transport {
    var endPoint: String { get }
    //didset으로 설정해도 될듯
    var lcoation: String { get set }
    var realTimeLocation: String { get }
    var durationTIme: Int { get }
    func currentLocation()
    func delieveryCompleted()
}

class TruckTransport: Transport {
    var endPoint: String = "우리집"
    var lcoation: String {
        get 
        {
            return self.endPoint
        }
        set
        {
            print("고객님이 배송지를 변경하였습니다")
            self.endPoint = newValue
        }
    }
    
    var realTimeLocation = "어딘가 잘 있다"
    var durationTIme: Int = 24
    
    func currentLocation() {
        print("현재 \(realTimeLocation) 에 있습니다")
    }
    
    func delieveryCompleted() {
        print("배송을 완료했습니다")
    }
    
}

class ShipTransport: Transport {
    var endPoint: String = "엄마집"
    var lcoation: String {
        get 
        { 
            return self.endPoint
        }
        set {
            print("고객님이 배송지를 변경하였습니다")
            self.endPoint = newValue
        }
    }
    
    var realTimeLocation = "해양 어딘가 잘 있다"
    var durationTIme: Int = 136
    
    
    func currentLocation() {
        print("현재 \(realTimeLocation) 에 있습니다")
    }
    
    func delieveryCompleted() {
        print("배송을 완료했습니다")
    }
}

class airplaneTransport: Transport {
    var endPoint: String = "공항"
    var lcoation: String {
        get 
        {
            return self.endPoint
        }
        set 
        {
            print("고객님이 배송지를 변경하였습니다")
            self.endPoint = newValue
        }
    }
    var realTimeLocation = "하늘 어딘가 잘 있다"
    var durationTIme: Int = 4
    
    
    func currentLocation() {
        print("현재 \(realTimeLocation) 에 있습니다")
    }
    
    func delieveryCompleted() {
        print("\(endPoint)으로 배송을 완료했습니다")
    }
}


enum TransportType{
case truck, ship, airplane
}

protocol TransportCreatorProtocol {
    func addTransport(transportType: TransportType) -> Transport
}

class TransportCreator : TransportCreatorProtocol {
    
    func addTransport(transportType: TransportType) -> Transport {
        switch transportType {
        case .truck:
            return TruckTransport()
        case .ship:
            return ShipTransport()
        case .airplane:
            return airplaneTransport()
        }
    }
}


let factory = TransportCreator()

var truck = factory.addTransport(transportType: .truck)
var ship = factory.addTransport(transportType: .ship)
var airplaine = factory.addTransport(transportType: .airplane)

ship.currentLocation()
truck.currentLocation()
airplaine.currentLocation()

 

 

참조

https://refactoring.guru/ko/design-patterns/factory-method/swift/example#example-1

 

스위프트로 작성된 팩토리 메서드 / 디자인 패턴들

사용 사례들: 팩토리 메서드 패턴은 스위프트 코드에서 널리 사용되며 코드에 높은 수준의 유연성을 제공해야 할 때 매우 유용합니다. 식별: 팩토리 메서드는 구상 클래스들로부터 객체들을 생

refactoring.guru

https://yagom.net/courses/design-pattern-in-swift/lessons/%ec%83%9d%ec%84%b1-%ed%8c%a8%ed%84%b4/topic/factory-method/

 

Design Pattern in Swift - 야곰닷넷

Design Pattern in Swift 디자인 패턴 코스에 오신 것을 환영합니다!  이 코스는 야곰 아카데미 3기 수료생들과 […]

yagom.net