지난 Singleton 패턴에 이어 이번에는 GoF 패턴 중 생성에 속해있는 Factory Method에 대해 작성해 보겠습니다.
구체적으로 어떤 인스턴스를 만들지는 서브 클래스가 결정하는 패턴을 말합니다.
ConcreteProduct
에서 생성한다.Creator
프로토콜을 채택한 타입에서는 각각 다른 타입을 반환합니다.Product 같은 경우 프로토콜을 이용해 구현할 수 있지만 제가 생각했을 때 좀 더 구체적인 타입에서 인스턴스를 생성한다는 말이 상속이 어울리는 것 같아 UML을 위에 이미지처럼 작성했습니다.
하나의 Factory에서 모든 자동차를 만드는 코드를 작성해 보겠습니다.
패턴 적용 전
class Car {
var name: String
var color: String
init(name: String, color: String) {
self.name = name
self.color = color
}
}
class CarFactory {
func makeCar(name: String, color: String) -> Car? {
if name == "k3" {
return Car(name: name, color: color)
} else if name == "K5" {
return Car(name: name, color: color)
} else if name == "K7" {
return Car(name: name, color: color)
}
return nil
}
}
위 코드를 보면 CarFactory에서 makeCar
메서드를 이용해 자동차를 만들고 있습니다.
위 코드의 문제점
1. makeCar
메서드 내부에서 분기 처리를 통해 각각 다른 인스턴스를 생성해야 한다.
2. 특별한 프로퍼티를 갖는 객체를 만들고 싶을 때 기존에 구현한 Car를 수정해야 한다. 그렇다면 의도하지 않은 변경이 발생할 수 있고 기존의 코드를 변경해야 한다.
기존의 코드를 변경하게 되면 객체지향 원칙 중 OCP를 위반하게 된다
(Car
를 확장하기 위해서는 계속해서 기존 코드를 변경해야 한다.)
OCP?
확장에는 열려 있어야 하지만 변경에는 닫혀 있어야 하는 객체 지향 다섯 가지 원칙 중 하나이다.
패턴 적용
protocol Factory {
func orederCar(color: String) -> Car
func makeCar(color: String) -> Car
}
extension Factory {
func orederCar(color: String) -> Car {
prepare()
let car = makeCar(color: color)
success()
return car
}
private func prepare() {
print("차를 준비중입니다.")
}
private func success() {
print("완성했습니다.")
}
}
orederCar
메서드 같은 경우 프로토콜을 확장해 기본 구현을 해주었다.
class K3Factory: Factory {
func makeCar(color: String) -> Car {
return K3(name: "K3", color: color)
}
}
class CarnivalFactory: Factory {
func makeCar(color: String) -> Car {
return Carnival(option: "추가", color: color)
}
}
각 Factory에서 다른 인스턴스를 생성해 반환한다.
class Car {
var name: String
var color: String
init(name: String, color: String) {
self.name = name
self.color = color
}
}
class K3: Car { }
class Carnival: Car {
let option: String
init(option: String, color: String) {
self.option = option
super.init(name: "Carnival", color: color)
}
}
let myCar = K3Factory().orederCar(color: "White")
let secondCar = CarnivalFactory().orederCar(color: "black") as! Carnival
타입 캐스팅 을 통해 구체적인 타입으로 변환하고 해당 타입에 있는 메소드를 사용하거나 프로퍼티에 접근할 수 있습니다.
이제 Factory Method의 장점과 단점을 알아보겠습니다.
🤔 프로토콜에 의존하기 때문에 관계를 느슨하게 유지하는 걸까?
상속을 사용 하더라도 상위 클래스에 의존하기 때문에 관계를 느슨하게 유지 할 수 있는지가 궁금합니다...... (아시는 분은 댓글로 남겨주세요 😭)
Creator
를 알고 있어야 합니다.해당 글은 인프런의 코딩으로 학습하는 GoF 디자인 패턴 강의와 블로그를 참고해 작성했습니다.