
스위프트로 작성된 팩토리 메서드
팩토리 메서드는 제품 객체들의 구상 클래스들을 지정하지 않고 해당 제품 객체들을 생성할 수 있도록 하는 생성 디자인 패턴입니다.
팩토리 메서드는 메서드를 정의하며, 이 메서드는 직접 생성자 호출(new
연산자)을 사용하여 객체를 생성하는 대신 객체 생성에 사용되여야 합니다. 자식 클래스들은 이 메서드를 오버라이드하여 생성될 객체들의 클래스를 변경할 수 있습니다.
다양한 팩토리 패턴들과 개념들의 차이점을 이해하지 못하셨다면 팩토리 비교를 읽어보세요.
복잡도:
인기도:
사용 사례들: 팩토리 메서드 패턴은 스위프트 코드에서 널리 사용되며 코드에 높은 수준의 유연성을 제공해야 할 때 매우 유용합니다.
식별: 팩토리 메서드는 구상 클래스들로부터 객체들을 생성하는 생성 메서드들로 인식될 수 있습니다. 구상 클래스들은 객체 생성 중에 사용되지만 팩토리 메서드들의 반환 유형은 일반적으로 추상 클래스 또는 인터페이스로 선언됩니다.
개념적인 예시
이 예시는 팩토리 메서드의 구조를 보여주고 다음 질문에 중점을 둡니다:
- 패턴은 어떤 클래스들로 구성되어 있나요?
- 이 클래스들은 어떤 역할을 하나요?
- 패턴의 요소들은 어떻게 서로 연관되어 있나요?
이 패턴의 구조를 배우면 실제 스위프트 언어 사용 사례를 기반으로 하는 다음 예시를 더욱 쉽게 이해할 수 있을 것입니다.
Example.swift: 개념적인 예시
import XCTest /// The Creator protocol declares the factory method that's supposed to return a /// new object of a Product class. The Creator's subclasses usually provide the /// implementation of this method. protocol Creator { /// Note that the Creator may also provide some default implementation of /// the factory method. func factoryMethod() -> Product /// Also note that, despite its name, the Creator's primary responsibility /// is not creating products. Usually, it contains some core business logic /// that relies on Product objects, returned by the factory method. /// Subclasses can indirectly change that business logic by overriding the /// factory method and returning a different type of product from it. func someOperation() -> String } /// This extension implements the default behavior of the Creator. This behavior /// can be overridden in subclasses. extension Creator { func someOperation() -> String { // Call the factory method to create a Product object. let product = factoryMethod() // Now, use the product. return "Creator: The same creator's code has just worked with " + product.operation() } } /// Concrete Creators override the factory method in order to change the /// resulting product's type. class ConcreteCreator1: Creator { /// Note that the signature of the method still uses the abstract product /// type, even though the concrete product is actually returned from the /// method. This way the Creator can stay independent of concrete product /// classes. public func factoryMethod() -> Product { return ConcreteProduct1() } } class ConcreteCreator2: Creator { public func factoryMethod() -> Product { return ConcreteProduct2() } } /// The Product protocol declares the operations that all concrete products must /// implement. protocol Product { func operation() -> String } /// Concrete Products provide various implementations of the Product protocol. class ConcreteProduct1: Product { func operation() -> String { return "{Result of the ConcreteProduct1}" } } class ConcreteProduct2: Product { func operation() -> String { return "{Result of the ConcreteProduct2}" } } /// The client code works with an instance of a concrete creator, albeit through /// its base protocol. As long as the client keeps working with the creator via /// the base protocol, you can pass it any creator's subclass. class Client { // ... static func someClientCode(creator: Creator) { print("Client: I'm not aware of the creator's class, but it still works.\n" + creator.someOperation()) } // ... } /// Let's see how it all works together. class FactoryMethodConceptual: XCTestCase { func testFactoryMethodConceptual() { /// The Application picks a creator's type depending on the /// configuration or environment. print("App: Launched with the ConcreteCreator1.") Client.someClientCode(creator: ConcreteCreator1()) print("\nApp: Launched with the ConcreteCreator2.") Client.someClientCode(creator: ConcreteCreator2()) } }
Output.txt: 실행 결과
App: Launched with the ConcreteCreator1. Client: I'm not aware of the creator's class, but it still works. Creator: The same creator's code has just worked with {Result of the ConcreteProduct1} App: Launched with the ConcreteCreator2. Client: I'm not aware of the creator's class, but it still works. Creator: The same creator's code has just worked with {Result of the ConcreteProduct2}
실제 사례 예시
Example.swift: 실제 사례 예시
import XCTest class FactoryMethodRealWorld: XCTestCase { func testFactoryMethodRealWorld() { let info = "Very important info of the presentation" let clientCode = ClientCode() /// Present info over WiFi clientCode.present(info: info, with: WifiFactory()) /// Present info over Bluetooth clientCode.present(info: info, with: BluetoothFactory()) } } protocol ProjectorFactory { func createProjector() -> Projector func syncedProjector(with projector: Projector) -> Projector } extension ProjectorFactory { /// Base implementation of ProjectorFactory func syncedProjector(with projector: Projector) -> Projector { /// Every instance creates an own projector let newProjector = createProjector() /// sync projectors newProjector.sync(with: projector) return newProjector } } class WifiFactory: ProjectorFactory { func createProjector() -> Projector { return WifiProjector() } } class BluetoothFactory: ProjectorFactory { func createProjector() -> Projector { return BluetoothProjector() } } protocol Projector { /// Abstract projector interface var currentPage: Int { get } func present(info: String) func sync(with projector: Projector) func update(with page: Int) } extension Projector { /// Base implementation of Projector methods func sync(with projector: Projector) { projector.update(with: currentPage) } } class WifiProjector: Projector { var currentPage = 0 func present(info: String) { print("Info is presented over Wifi: \(info)") } func update(with page: Int) { /// ... scroll page via WiFi connection /// ... currentPage = page } } class BluetoothProjector: Projector { var currentPage = 0 func present(info: String) { print("Info is presented over Bluetooth: \(info)") } func update(with page: Int) { /// ... scroll page via Bluetooth connection /// ... currentPage = page } } private class ClientCode { private var currentProjector: Projector? func present(info: String, with factory: ProjectorFactory) { /// Check whether the client code is already presenting something... guard let projector = currentProjector else { /// 'currentProjector' variable is nil. Create a new projector and /// start presentation. let projector = factory.createProjector() projector.present(info: info) self.currentProjector = projector return } /// Client code already has a projector. Let's sync pages of the old /// projector with a new one. self.currentProjector = factory.syncedProjector(with: projector) self.currentProjector?.present(info: info) } }
Output.txt: 실행 결과
Info is presented over Wifi: Very important info of the presentation Info is presented over Bluetooth: Very important info of the presentation