Factory Pattern
Factory pattern이라는 말 그 자체로 객체를 생성해주는 역할을 한다. Factory pattern의 정의는 다음과 같다.
객체를 생성하기 위한 인터페이스를 정의하는데, 어떤 클래스의 인스턴스를 만들지는 서브 클래스에서 결정하게 만드는 패턴이다.
즉 객체를 통해 인스턴스를 만드는 것이 아니라 Factory method를 통해 인스턴스를 생성하고 사용하게 되는 것이다. 어떤 과정으로 인스턴스가 생성되는지 그 과정에서 알아야 하는 세부 사항들을 줄여주는 효과가 있다.
예를 들어 storyboard를 기반으로 View Controller의 인스턴스를 만들려고 할 때 알아야 하는 값이 두 가지 있다. 바로 storyboard의 name과 identifier이다.
매번 View Controller를 생성할 때 마다 storyboard 각각의 name과 identifier를 기억하고 있기는 힘들다. 이에 이런 부분을 factory method에 담아 클라이언트는 인스턴스가 어떻게 생기게 되는지 모르게 되고 그저 반환되는 인스턴스를 사용하게 된다. 즉 클라이언트 입장에서는 storyboard의 name과 identifier를 모르고 factory만 알면, 원하는 View Controller의 인스턴스를 생성하여 사용할 수 있다는 것이다.
즉 동일한 공정을 이용해서 서로 다른 인스턴스를 만드는 것과 같다.
Factory Pattern
의 장점- 확장성이 높다.
- 의존성/결합도가 낮아진다.
- 인스턴스의 사용과 생성을 분리할 수 있다.
자 이제 어떤 이유에서 factory pattern을 사용해야하는지 예제를 보면서 살펴보자.
탈 것을 예로 들어서 이야기해보자.
우선 탈 수 있다는 것을 나타내기 위한 Rideable
프로토콜을 정의해준다.
protocol Rideable {
var brandName: String { get }
func ride()
}
자 이제 이 프로토콜을 채택하는 차, 자전거 타입을 생성해보자.
struct Car: Rideable {
var brandName: String
func ride() {
print("\(self.brandName)타고 가자!")
}
}
struct Bicycle: Rideable {
var brandName: String
func ride() {
print("\(self.brandName)타고 가자!")
}
}
보통은 이렇게까지 만들고 필요한 부분에서 Car
나 Bicycle
을 초기화하여 인스턴스를 생성해서 사용했을 것이다.
만약 어떤 특정 View Controller에서 인스턴스를 생성해서 사용했다고 가정해보자.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let bmw = Car(brandName: "BMW")
bmw.ride() // BMW타고 가자!
}
}
자 그런데 만약에 Car
에 새로운 프로퍼티가 추가되어서 이니셜라이저가 변경되는 경우를 생각해보자. 만약 price
라는 프로퍼티가 추가되었다고 생각해보자.
그렇다면 타입을 수정하게 되고, 이에 따라 불가피하게 해당 타입의 인스턴스를 생성했던 부분의 수정 또한 필요하게 된다. 지금은 한 군데에만 사용을 했지만.... 만약 10개의 View Controller에서 Car
타입의 인스턴스를 생성했다면 일일이 찾아가서 초기화 부분을 모두 다 수정해줘야 할 것이다.
이런 부분에 있어서 효율적으로 사용할 수 있는 것이 바로 Factory pattern이다.
위에서 말한 것 처럼 직접 인스턴스를 생성하는 것이 아니라 Factory를 통해 인스턴스를 생성해주는 것이다.
enum VehicleType {
case car
case bicycle
}
class VehicleFactory {
func creatVehicle(brandName: String, vehicleType: VehicleType) -> Rideable {
switch vehicleType {
case .car:
return Car(brandName: brandName)
case .bicycle:
return Bicycle(brandName: brandName)
}
}
}
이렇게 Factory 타입을 만들어줄 수 있다. 이제 실제로 사용되는 부분에서는 인스턴스화 되는 과정을 세세하게 알지 못해도 인스턴스를 생성하고 사용할 수 있게 된다.
아까의 View Controller의 코드가 아래처럼 바뀔 수 있다.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let vehicleFactory = VehicleFactory()
let bmw = vehicleFactory.creatVehicle(brandName: "BMW", vehicleType: .car)
bmw.ride() // BMW타고 가자!
}
}
이제는 Car
의 이니셜라이저가 아무리 바뀌어도 ViewController에서는 신경써주지 않아도 된다. 딱 한 군데, Factory method 부분만 수정해주면 된다. 이러한 부분에서 확장성과 재사용성이 높다고 말할 수 있을 것 같다. 또한 직접적으로 Car
에 접근하는 것이 아니라 Factory를 거치기 때문에 의존성/결합도 또한 낮아지는 것을 확인할 수 있다.
결론적으로. 이제 직접 각 객체를 통해 인스턴스를 만들지 않고 Factory를 통해 인스턴스를 만들 수 있다는 것이 큰 장점이다.
Abstract Factory Pattern
이를 "추상 팩토리 패턴"이라고 많이 부르는 것 같다. Abstract Factory Pattern은 앞서 봤던 Factory Pattern과 유사한데, 차이점이라고 하면 Abstract Factory Pattern은 Factory를 만드는 상위 클래스가 있다는 점이다.
즉 쉽게 말하면 Factory를 만드는 더 상위의 Factory가 있는 셈이다.
앞선 예제를 변형해보면서 이해해보자.
차를 만드는 회사 중에 다들 알고 있을 만한 회사로 예시를 들어보자. BMW
와 Benz
가 있다고 해보자. 이 둘은 모두 이동수단을 만드는 역할을 한다.
만약에 이제부터 차 말고도 자전거도 같이 만들어야 한다고 하는 경우라고 가정해보자.
protocol VehicleFactory {
func createCar() -> Car
func createBicycle() -> Bicycle
}
이를 활용하여 각 제조사별 Factory를 생성할 수 있다.
class BMWFactory: VehicleFactory {
func createCar() -> Car {
return Car(brandName: "BMW")
}
func createBicycle() -> Bicycle {
return Bicycle(brandName: "BMW")
}
}
class MercedesBenzFactory: VehicleFactory {
func createCar() -> Car {
return Car(brandName: "Mercedes-Benz")
}
func createBicycle() -> Bicycle {
return Bicycle(brandName: "Mercedes-Benz")
}
}
사실 이렇게 각 Factory를 만든 것 까지 Factory Pattern
이라고 볼 수 있다. 하지만 지금은 N개의 Factory가 만들어질 수 있는 상황이고 이를 묶어주는 상위의 Factory를 보기 위함이다.
이제 이를 가지고 무난하게 사용해볼 수 있다.
let bmwFactory = BMWFactory()
let bmwCar = bmwFactory.createCar()
let bmwBicycle = bmwFactory.createBicycle()
Mercedes-Benz
도 마찬가지로 동일하게 사용해줄 수 있는데, 이제 제조사 또한 신경을 크게 쓰지 않아도 되도록 상위 Factory인 ManufacturerFactory
를 만들어 볼 것이다.
class ManufacturerFactory {
static func createFactory(manufacturer: VehicleManufacturer) -> VehicleFactory {
switch manufacturer {
case .bmw:
return BMWFactory()
case .mercedesBenz:
return MercedesBenzFactory()
}
}
}
이제 이 상위 Factory를 가지고 Factory 객체를 만들어 줄 수 있다. 이제 그 다음부터는 Factory pattern 때 사용하던 방식과 동일하다.
Abstract Factory Pattern
의 장점- 확장성이 높다.
- 객체간의 의존성/결합도가 낮아진다.
- SOLID의 SRP, OCP를 따른다.
- 생성자 코드를 분리하여 코드의 역할이 분배되었다
- 새로운 타입 추가에 자유롭다
- 단점
- 너무 깊어지면 찾기가 어렵다..!
이렇게 Factory Pattern에 대해 알아보았는데, 이를 실제 View Controller를 생성한다거나, 특정 객체를 생성할 때 적용해서 장점을 취하면 좋을 것 같다.
-Ref-
https://medium.com/@sreeks/view-controller-factory-in-swift-1d4ee8f9b54
https://stevenpcurtis.medium.com/the-factory-pattern-using-swift-b534ae9f983f
https://velog.io/@ellyheetov/Factory-Pattern
https://jeonyeohun.tistory.com/211