[Swift Design Pattern 03] Factory Method
Factory Method (팩토리 메소드) - Creational Pattern (생성 패턴)
Factory Method는 통합 클래스와 기본 클래스 사이의 프로토콜을 결정할 때 사용된다.
Product
Creator 와 Creator의 서브 클래스에 의해 생성되는 클래스에게 공통적인 인터페이스를 제공한다.
Concrete Product
Product 인터페이스의 다른 구현체
Creator
Creator 클래스는 새 Product 클래스를 리턴하는 팩토리 메소드를 선언합니다. 이 리턴 타입은 Product 인터페이스와 일치해야합니다. 팩토리 메소드를 추상적(abstract)로 선언하여 모든 서브 클래스가 자체 메소드를 구현할 수 있습니다.
Concrete Creators
Concrete Creators는 기본 팩토리 메소드를 재정의하여 다른 타입의 Product를 반환합니다.
+
팩토리 메소드는 상속하는 객체에 대해 다루는데 Swift에서는 Class만 상속된다.
이 글은 객체 = 클래스 라고 가정하고 작성했다.
When
1. 클래스를 만드는데 어떤 서브 클래스를 만들지 아직 정확히 모르는 경우
추후에 확장할 때 쉽고 효율적으로 하기 위해 팩토리 메소드를 사용한다
2. 라이브러리 또는 프레임워크의 사용자에게 내부 구성요소를 확장하는 방법을 제공할 경우
상속을 통해 라이브러리와 프레임워크의 기능을 쉽게 확장할 수 있다. 이 때 팩토레 메소드를 사용함으로써 사용자가 쉽게 재정의할 수 있도록 한다.
3. 매번 클래스를 재구성하는 대신 기존 클래스를 재사용할 경우
기존 클래스를 재사용하거나 새 클래스를 생성이 필요한 경우 팩토리 메소드를 사용한다.
Example
이렇게 말하면 잘 모르겠으니 예시를 들어보겠다.
나는 지금 의류 브랜드에서 일하고 있고 자사 앱을 하나 만들었다.
이 앱은 택배로 국내 고객들의 집까지 배송을 해준다.
이 때까지 고객들의 배송 현황 관리를 위해 Truck 클래스를 만들어 관리했다.
시간이 지나 옷을 너무 잘 만들어서 브랜드가 유명해졌고 해외에서까지 옷을 구매하기 시작했다.
이제 해외 고객들의 집까지 배(ship)로 배송을 해주려고 한다.
배로 배송되는 현황을 관리하기 위해 Ship 클래스를 만들어야 한다.
처음부터 끝까지.... 너무 비효율적이다.
어찌어찌 해서 Ship 클래스를 만들었다고 하자.
그 다음에 비행기로 배송을 지원하기 위해 Plane 클래스를 만들 수도 있고, 퀵으로 배달하기 위해 autoBicycle 클래스를 만들어야 할 수도 있다.
하... 퇴사할까.....
이를 해결하기 위해 등장한 것이 팩토리 메서드이다.
생성자 CreateTransport() 호출을 Creator에 선언을 한다.
그리고 서브 클래스 RoadLogistics와 SeaLogistics에서 메소드를 재선언한다.
이 때 서브클래스는 같은 기본 클래스를 상속받거나 같은 인터페이스를 가지고 있어야 한다.
Product에서는 운송 수단 클래스 Transport를 선언한다.
Truck과 Ship은 Transport를 상속받는데 deliver()라는 동일한 interface를 가져야 한다.
이런 식으로 팩토리 메소드를 선언함으로써 추후에 서브 클래스가 생길 때 더 쉽게 확장을 할 수 있다.
Code
Product에 해당하는 프로토콜을 선언합니다.
protocol Transport {
var location: String { get }
func getTransport()
func setLocation(location: String)
func getLocation()
func deilver()
}
Product 프로토콜을 준수하는 Concrete Protocol을 선언합니다.
class TruckTransport: Transport {
var location = "회사 창고"
func getTransport() {
print("트럭이 고객님의 제품을 운반 중입니다.")
}
func setLocation(location: String) {
self.location = location
}
func getLocation() {
print("현재 위치는 \(location) 입니다.")
}
func deilver() {
print("고객님에게 배송을 완료했습니다")
}
}
class ShipTransport: Transport {
var location = "회사 창고"
func getTransport() {
print("컨테이너 선이 고객님의 제품을 운반 중입니다.")
}
func setLocation(location: String) {
self.location = location
}
func getLocation() {
print("현재 위치는 \(location) 입니다.")
}
func deilver() {
print("고객님에게 배송을 완료했습니다")
}
}
이제 Creator 를 프로토콜로 선언합니다.
Creator는 항상 같은 타입을 리턴합니다. 이 예제에서는 Tranport를 리턴합니다.
protocol TransportFactoryProtocol {
func createTransport(tranportType: TransportType) -> Transport
}
enum TransportType {
case truck
case ship
}
이제 Concrete Creator = Factory를 만들어줍니다.
class TransportFactory: TransportFactoryProtocol {
func createTransport(tranportType: TransportType) -> Transport {
switch tranportType {
case .truck:
return TruckTransport()
case .ship:
return ShipTransport()
}
}
}
이제부터 Factory 객체를 통해 인스턴스를 만들어주면 됩니다.
let factory = TransportFactory()
let truckDeliver = factory.createTransport(tranportType: .truck)
let shipDeliver = factory.createTransport(tranportType: .ship)
truckDeliver.getTransport()
truckDeliver.getLocation()
print("------")
shipDeliver.getTransport()
shipDeliver.getLocation()
shipDeliver.setLocation(location: "곤지암 허브")
shipDeliver.getLocation()
이제 새로운 운송수단이 생긴다면 새로운 Product와 팩토리를 추가해주면 됩니다.
class TruckTransport: Transport {
var location = "회사 창고"
func getTransport() {
print("비행기가 고객님의 제품을 운반 중입니다.")
}
func setLocation(location: String) {
self.location = location
}
func getLocation() {
print("현재 위치는 \(location) 입니다.")
}
func deilver() {
print("고객님에게 배송을 완료했습니다")
}
}
enum TransportType {
case truck
case ship
case plane
}
class TransportFactory: TransportFactoryProtocol {
func createTransport(tranportType: TransportType) -> Transport {
switch tranportType {
case .truck:
return TruckTransport()
case .ship:
return ShipTransport()
case .plane:
return PlaneTransport()
}
}
}
Result
팩토리 메소드의 장점
1. 클래스의 유지보수가 쉬워진다.
Creator 와 Concrete Product간의 결합도를 낮춘다. 결합도가 낮아짐에 따라 유지보수가 쉬워진다.
코드에서 봤듯이 프로토콜을 제공함에 따라 새로운 클래스를 더 쉽게 추가할 수 있다.
2. Product 생성 코드를 한 곳으로 모아 코드를 더 쉽고 체계적으로 관리할 수 있다.
단점
1. 코드가 복잡해진다
계속해서 서브 클래스를 정의해야하기 때문에 코드가 복잡해진다.
팩토리 메소드 끗 😎
참고
GOF의 디자인 패턴 / 에릭 감마 외
https://refactoring.guru/design-patterns/factory-method
https://icksw.tistory.com/237?category=944177