소프트웨어 개발에서 객체 생성은 중요한 과정 중 하나입니다.
객체 생성을 효과적으로 처리하고, 코드의 유연성과 재사용성을 높이기 위해 다양한 디자인 패턴이 사용됩니다.
이번 글에서는 iOS 앱 개발에서 Factory 패턴에 대해 알아보고, 실제 사용 예시를 살펴보겠습니다.
Factory Method 개요
Factory 패턴은 객체 생성을 위한 디자인 패턴 중 하나로, 다음과 같은 구성 요소로 이루어집니다:
- Creator: 객체를 생성하는 추상 클래스 또는 인터페이스입니다. 객체의 인스턴스를 반환하는 Factory Method를 정의합니다.
- ConcreteCreator: Creator를 상속받은 클래스로, 객체 생성을 담당하는 구체적인 Factory Method를 구현합니다.
- Product: 생성될 객체의 추상 클래스 또는 인터페이스입니다. ConcreteCreator가 생성하는 객체의 공통된 기능을 정의합니다.
- ConcreteProduct: Product를 상속받은 클래스로, 실제로 생성되는 객체를 구현합니다.
Factory 패턴의 핵심 아이디어는 객체 생성을 Creator 클래스로부터 분리하고, ConcreteCreator 클래스에서 객체 생성에 대한 구체적인 결정을 내리는 것입니다.
이를 통해 객체 생성의 변화에 유연하게 대응할 수 있고, 코드의 재사용성과 확장성을 높일 수 있습니다.
Factory 패턴의 예시
Factory 패턴으로 짠 코드와 그렇지 않은 코드를 비교하며 iOS 앱에서 Factory 패턴이 활용될 수 있는 예시를 살펴보겠습니다.
앱 내에서 다양한 테마의 버튼을 생성해야 한다고 가정합니다.
Factory를 사용하지 않은 예시
protocol Button {
func render()
}
class LightButton: Button {
func render() {
// Light 테마의 버튼을 렌더링하는 로직
}
}
class DarkButton: Button {
func render() {
// Dark 테마의 버튼을 렌더링하는 로직
}
}
enum Theme {
case light
case dark
}
class ViewController {
var theme: Theme
init(theme: Theme) {
self.theme = theme
}
func createButton() -> Button {
switch theme {
case .light:
return LightButton()
case .dark:
return DarkButton()
}
}
}
위의 코드에서는 Button 프로토콜을 정의하고, LightButton과 DarkButton 클래스가 해당 프로토콜을 구현합니다.
또한, ViewController 클래스에서 테마에 따라 버튼을 생성하는 createButton 메서드를 구현합니다.
이렇게 구현된 버튼을 실제로 사용할 때는 아래처럼 사용하게 됩니다.
class ViewController: UIViewController {
var theme: Theme
init(theme: Theme) {
self.theme = theme
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let button = createButton()
button.render()
}
func createButton() -> Button {
switch theme {
case .light:
return LightButton() // dependency
case .dark:
return DarkButton() // dependency
}
}
}
위의 코드에서는 ViewController 안에서, createButton 메서드를 호출하여 테마에 따라 버튼 객체를 생성합니다.
생성된 버튼 객체를 사용하여 각각의 테마에 맞게 버튼을 렌더링합니다.
이 방식은 버튼 생성 로직이 ViewController 클래스 내부에 직접 구현되어 있으므로 객체 생성과 테마 결정이 강하게 결합됩니다.
따라서 코드의 확장성과 유연성이 낮아지고, 특정 테마에 종속된 코드가 많아지는 단점이 있습니다.
물론, 의존성이 높기 때문에 테스트 하기에도 용이하지 않습니다.
Factory Method를 사용한 예시
그럼 위의 예시를 Factory 패턴으로 리팩토링 해보겠습니다.
protocol Button {
func render()
}
class LightButton: Button {
func render() {
// Light 테마의 버튼을 렌더링하는 로직
}
}
class DarkButton: Button {
func render() {
// Dark 테마의 버튼을 렌더링하는 로직
}
}
class ButtonFactory {
static func createButton(theme: Theme) -> Button {
switch theme {
case .light:
return LightButton()
case .dark:
return DarkButton()
}
}
}
enum Theme {
case light
case dark
}
위에서 했던 것과 동일하게 Button 프로토콜을 정의하고, 이 프로토콜을 상속 받는 LightButton과 DarkButton을 만듭니다.
그리고 아까와는 다르게, 버튼을 생성하는 버튼 공장 class를 하나 추가로 만듭니다.
ButtonFactory 클래스를 만들고 그 안에 버튼을 생성하는 Factory Method인 createButton을 static 함수로 구현했습니다.
그럼 이렇게 사용할 수 있습니다.
class ViewController: UIViewController {
var theme: Theme
init(theme: Theme) {
self.theme = theme
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
let button = ButtonFactory.createButton(theme: theme)
button.render()
}
}
위의 코드에서는 ButtonFactory를 사용하여 createButton 메서드를 호출하여 테마에 따라 버튼 객체를 생성합니다.
이를 통해 각각의 버튼 객체가 필요한 테마에 맞게 생성되고 사용됩니다.
ViewController 내에서 LightButton()이나 DarkButton()으로 객체를 직접 생성하고 있지 않기 때문에 의존성이 제거가 되었고, 테스트가 용이해졌을 것입니다.
그리고 또, 새로운 버튼 종류가 추가되더라도 ViewController는 건들 필요 없이 ButtonFactory 내부의 구체적인 생성 로직만 수정하면 되므로, 코드의 확장성과 유지보수성이 높아집니다. (기존 방법대로라면 switch 안에 enum case를 추가해주어야 했을 겁니다)
'iOS > 디자인패턴' 카테고리의 다른 글
[iOS 디자인패턴] Singleton 패턴에 대해 (0) | 2023.05.11 |
---|---|
[iOS 디자인패턴] Observer 패턴과 Notification 설계 (0) | 2023.05.11 |
[iOS 디자인패턴] Delegate 패턴과 Protocol에 대해 (0) | 2023.05.11 |