[iOS 8주차] Project PokeContact: MVVM-C 아키텍처 도입

DoyleHWorks·2024년 12월 9일
1

https://github.com/SpartaCoding-iOS05-i/PokeContact/pull/8

Project: PokeContact

MVVM-C 아키텍처 도입하기

큰 틀 구성

1️⃣ 폴더 구조 설정

PokeContact/
├── App/
│   ├── AppDelegate.swift
│   ├── SceneDelegate.swift
│   ...
├── Coordinators/
├── Models/
├── Views/
├── ViewModels/
...

2️⃣ Coordinator 프로토콜 정의

protocol Coordinator {
    var childCoordinators: [Coordinator] { get set }
    func start()
}
  • childCoordinators: 하위 Coordinator를 관리하여 메모리 누수를 방지
  • start(): 각 Coordinator가 수행해야 할 초기 동작

3️⃣ 메인 코디네이터 작성

앱의 진입점이 되는 AppCoordinator를 생성한다:

import UIKit

class AppCoordinator: Coordinator {
    var childCoordinators: [Coordinator] = []
    private let window: UIWindow
    
    init(window: UIWindow) {
        self.window = window
    }
    
    func start() {
        let navigationController = UINavigationController()
        let mainCoordinator = MainCoordinator(navigationController: navigationController)
        childCoordinators.append(mainCoordinator)
        mainCoordinator.start()
        
        window.rootViewController = navigationController
        window.makeKeyAndVisible()
    }
}

4️⃣ 화면별 Coordinator 생성

화면마다 별도의 Coordinator를 만든다: MainCoordinator

import UIKit

class MainCoordinator: Coordinator {
    var childCoordinators: [Coordinator] = []
    private let navigationController: UINavigationController
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    internal func start() {
        let viewModel = MainViewModel()
        let viewController = MainViewController(viewModel: viewModel)
        viewModel.coordinator = self
        navigationController.pushViewController(viewController, animated: true)
    }
}

5️⃣ ViewModel과 ViewController 연결

ViewModel은 View의 상태 및 로직을 관리한다:

MainViewModel

class MainViewModel {
    weak var coordinator: MainCoordinator?

    func didTapNext() {
        coordinator?.navigateToDetail()
    }
}

MainViewController

import UIKit
import SnapKit
import Then

class MainViewController: UIViewController {
    private let viewModel: MainViewModel

    init(viewModel: MainViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }

    private func configureUI() {
        view.backgroundColor = .systemPink
        
        let button = UIButton(type: .system).then {
            $0.setTitle("Button", for: .normal)
            $0.addTarget(self, action: #selector(didTapButton), for: .touchUpInside)
        }
        
        [
            button,
        ].forEach { view.addSubview($0) }

        button.snp.makeConstraints {
            $0.center.equalToSuperview()
            $0.width.equalTo(120)
            $0.height.equalTo(44)
        }
    }
    
    @objc private func didTapButton() {
        viewModel.didTapNext()
    }
}

6️⃣ 추가적인 화면 전환 처리

MainCoordinator에 다음 화면으로 전환하는 로직을 추가함:

extension MainCoordinator {
    func navigateToDetail() {
        let detailViewModel = DetailViewModel()
        let detailViewController = DetailViewController(viewModel: detailViewModel)
        navigationController.pushViewController(detailViewController, animated: true)
    }
}

7️⃣ SceneDelegate에서 시작

앱의 진입점을 AppCoordinator로 설정함:

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?
    var appCoordinator: AppCoordinator?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        let window = UIWindow(windowScene: windowScene)
        self.window = window

        let appCoordinator = AppCoordinator(window: window)
        self.appCoordinator = appCoordinator
        appCoordinator.start()
    }
}


MainCoordinator, MainViewModel 사이의 결합도 낮추기

프로토콜 사용

1️⃣ Protocol 정의

MainCoordinator의 역할을 나타내는 MainCoordinatorProtocol 프로토콜을 정의한다:
(MainViewModel은 이 프로토콜에 의존하여 MainCoordinator와 통신함)

protocol MainCoordinatorProtocol: AnyObject {
    func navigateToDetail()
}

MainViewModel은 MainCoordinatorProtocol에만 의존하므로, 실제 MainCoordinator가 무엇인지 알 필요가 없다.


2️⃣ MainCoordinator에서 Protocol 준수

MainCoordinator에 위에서 정의한 프로토콜을 채택시키고 구현한다:

extension MainCoordinator: MainCoordinatorProtocol {
    internal func navigateToDetail() {
        let detailViewModel = DetailViewModel(coordinator: DetailCoordinator())
        let detailViewController = DetailViewController(viewModel: detailViewModel)
        navigationController.pushViewController(detailViewController, animated: true)
    }
}

3️⃣ MainViewModel에서 Protocol에 의존

MainViewModel은 Coordinator의 구체적인 타입을 참조하지 않고, Protocol에 의존하여 동작한다:

class MainViewModel {
    private weak var coordinator: MainCoordinatorProtocol?

    init(coordinator: MainCoordinatorProtocol) {
        self.coordinator = coordinator
    }

    func didTapNavigate() {
        coordinator?.navigateToDetail()
    }
}
  • 의존성 주입(DI): MainViewModel은 Coordinator를 직접 생성하지 않고, 생성자에서 MainCoordinatorProtocol을 주입받음
  • 약한 참조(weak): ViewModel이 Coordinator를 강하게 참조하지 않으므로, 메모리 누수가 방지됨

4️⃣ MainViewController에서 MainViewModel 사용

MainViewControllerMainViewModel에만 의존하며, MainViewModelMainCoordinator와의 통신을 처리한다:

import UIKit
import SnapKit
import Then

class MainViewController: UIViewController {
    private let viewModel: MainViewModel

    init(viewModel: MainViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
    }
    
    // MARK: - UI Configuration
}


5️⃣ MainCoordinator에서 MainViewController와 MainViewModel 초기화

MainCoordinatorMainViewModel에 자신을 주입하고, MainViewController를 초기화한다:

func start() {
    let viewModel = MainViewModel(coordinator: self) // 의존성 주입
    let viewController = MainViewController(viewModel: viewModel)
    navigationController.pushViewController(viewController, animated: true)
}


MainViewModel과 MainViewController의 결합도 낮추기

Delegate 패턴 (프로토콜) 사용

MainViewModel.swift

protocol MainViewModelDelegate: AnyObject {
    //
}

class MainViewModel {
    private weak var coordinator: MainCoordinatorProtocol?
    weak var delegate: MainViewModelDelegate?
    
    init(coordinator: MainCoordinatorProtocol) {
        self.coordinator = coordinator
    }
    
    func didTapNavigate() {
        coordinator?.navigateToDetail()
    }
}

MainViewController.swift

import UIKit
import SnapKit
import Then

class MainViewController: UIViewController, MainViewModelDelegate {
    private let viewModel: MainViewModel

    init(viewModel: MainViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
        viewModel.delegate = self
    }
    
    // MARK: - UI Configuration
}

DetailViewController, DetailViewModel, DetailCoordinator 결합도 낮추기

프로토콜 & 간략화한 Factory 패턴 활용

1️⃣ Protocol 기반 의존성 주입

DetailViewModelDetailCoordinatorProtocol에 의존하도록 수정하고, DetailCoordinator는 생성자가 아닌 주입 방식으로 연결한다:

DetailCoordinator.swift

protocol DetailCoordinatorProtocol: AnyObject {
    func saveDetailData()
}

class DetailCoordinator: Coordinator {
    var childCoordinators: [Coordinator] = []
    func start() {
        //
    }
}

extension DetailCoordinator: DetailCoordinatorProtocol {
    func saveDetailData() {
        //
    }
}

DetailViewModel.swift

protocol DetailViewModelDelegate: AnyObject {
    //
}

class DetailViewModel {
    private weak var coordinator: DetailCoordinatorProtocol?
    weak var delegate: DetailViewModelDelegate?
    
    init(coordinator: DetailCoordinatorProtocol) {
        self.coordinator = coordinator
    }
    
    func saveData() {
        coordinator?.saveDetailData()
    }
}

2️⃣ MainCoordinator에서 구체 타입 생성 제거

MainCoordinator에서 DetailCoordinator를 직접 생성하지 않고, 의존성을 외부에서 주입받도록 수정한다:

MainCoordinator.swift

protocol MainCoordinatorProtocol: AnyObject {
    func navigateToDetail()
}

class MainCoordinator: Coordinator {
    var childCoordinators: [Coordinator] = []
    private let navigationController: UINavigationController
    private let detailCoordinatorFactory: () -> DetailCoordinatorProtocol
    
    init(
        navigationController: UINavigationController,
        detailCoordinatorFactory: @escaping () -> DetailCoordinatorProtocol
    ) {
        self.navigationController = navigationController
        self.detailCoordinatorFactory = detailCoordinatorFactory
    }
    
    internal func start() {
        let viewModel = MainViewModel(coordinator: self)
        let viewController = MainViewController(viewModel: viewModel)
        navigationController.pushViewController(viewController, animated: true)
    }
}

extension MainCoordinator: MainCoordinatorProtocol {
    internal func navigateToDetail() {
        let detailCoordinator = detailCoordinatorFactory()
        let detailViewModel = DetailViewModel(coordinator: detailCoordinator)
        let detailViewController = DetailViewController(viewModel: detailViewModel)
        navigationController.pushViewController(detailViewController, animated: true)
    }
}

3️⃣ Coordinator 생성 책임을 AppCoordinator로 이동

Coordinator 간의 의존성을 조정하는 책임을 AppCoordinator에게 위임한다:

AppCoordinator.swift

class AppCoordinator: Coordinator {
    var childCoordinators: [Coordinator] = []
    private let navigationController: UINavigationController
    private let window: UIWindow
    
    init(window: UIWindow, navigationController: UINavigationController) {
        self.window = window
        self.navigationController = navigationController
    }
    
    func start() {
        let mainCoordinator = MainCoordinator(
            navigationController: navigationController,
            detailCoordinatorFactory: { DetailCoordinator() }
        )
        childCoordinators.append(mainCoordinator)
        mainCoordinator.start()
        
        setupWindow()
    }
    
    private func setupWindow() {
        window.rootViewController = navigationController
        window.makeKeyAndVisible()
    }
}



What I've learned:

MVVM-C

MVVM-C(Model-View-ViewModel-Coordinator) 아키텍처는 iOS 개발에서 흔히 사용되는 디자인 패턴으로, MVVM(Model-View-ViewModel) 패턴에 Coordinator를 추가하여 모듈 간의 네비게이션과 의존성을 효율적으로 관리하는 구조이다.

구성 요소


1. Model

  • 앱의 비즈니스 로직과 데이터를 담당
  • 네트워크 레이어나 데이터베이스와의 상호작용을 처리
  • 단순한 데이터 구조와 로직만 포함

2. View

  • UI와 관련된 모든 작업을 처리함
  • ViewModel에서 전달받은 데이터를 렌더링하며, 사용자 입력 이벤트를 전달함
  • 사용자와 직접 상호작용

3. ViewModel

  • ViewModel 사이의 중간 관리자 역할
  • 데이터를 포맷하거나 변환하여 View에 전달
  • 비즈니스 로직은 없지만, 뷰에 필요한 데이터를 제공함

4. Coordinator

  • 네비게이션 로직과 모듈 간의 흐름을 담당
  • 뷰 컨트롤러 생성 및 전환을 관리하여 뷰와 뷰모델을 분리
  • 하나의 Coordinator는 일반적으로 특정 플로우(예: 로그인, 대시보드 등)를 담당함
  • 앱의 계층 구조를 명확히 하고, ViewControllerViewModel이 네비게이션 관련 코드를 가지지 않도록 함

MVVM-C의 장점

  1. 모듈화

    • 각 컴포넌트(View, ViewModel, Model, Coordinator)가 독립적으로 동작하여 코드의 재사용성이 높아짐
  2. 테스트 가능성

    • 비즈니스 로직과 네비게이션 로직이 View에서 분리되므로 단위 테스트가 더 용이해짐
  3. 유지보수성

    • 네비게이션 로직이 Coordinator에 집중되어 뷰 컨트롤러의 코드가 단순해지고 유지보수가 쉬워짐
  4. 의존성 역전 원칙(DIP) 준수

    • View는 ViewModel에 의존하고, ViewModel은 Model에 의존하여 상위 모듈과 하위 모듈 간의 결합도를 낮춤

MVVM-C 아키텍처의 데이터 흐름

  1. 사용자 입력 → View → ViewModel

    • View는 사용자로부터 입력을 받고 이를 ViewModel에 전달함
  2. ViewModel → Model

    • ViewModel은 필요한 데이터를 Model에서 가져오거나 처리 요청을 전달함
  3. Model → ViewModel

    • Model이 데이터를 반환하면, ViewModel이 이를 가공하여 View로 전달함
  4. Coordinator

    • Coordinator는 화면 전환 및 플로우 제어를 담당하여 View와 ViewModel이 네비게이션 세부사항을 신경 쓰지 않도록 함

간단한 코드 예제

로그인 플로우를 MVVM-C로 구현한다고 가정해보자.

1. Coordinator

import UIKit

class LoginCoordinator: Coordinator {
    var navigationController: UINavigationController
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        let viewModel = LoginViewModel()
        let loginViewController = LoginViewController(viewModel: viewModel)
        viewModel.coordinator = self // ViewModel이 Coordinator를 참조
        navigationController.pushViewController(loginViewController, animated: true)
    }
    
    func navigateToDashboard() {
        let dashboardCoordinator = DashboardCoordinator(navigationController: navigationController)
        dashboardCoordinator.start()
    }
}

2. ViewModel

import Foundation

class LoginViewModel {
    weak var coordinator: LoginCoordinator? // 약한 참조로 순환 참조 방지
    
    func login(username: String, password: String) {
        // 로그인 로직 (예: 네트워크 호출)
        if username == "test" && password == "password" {
            coordinator?.navigateToDashboard() // 성공 시 대시보드로 이동
        }
    }
}

3. View

import UIKit

class LoginViewController: UIViewController {
    private let viewModel: LoginViewModel
    
    init(viewModel: LoginViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        // UI 구성 및 버튼 액션 추가
    }
    
    @objc func loginButtonTapped() {
        viewModel.login(username: "test", password: "password")
    }
}

이처럼 MVVM-C 아키텍처를 사용하면, 각 컴포넌트의 역할이 명확히 분리되어 복잡한 프로젝트에서도 코드의 유지보수가 용이해진다. 필요에 따라 Combine이나 RxSwift를 도입하여 바인딩 작업을 개선할 수도 있다.



Factory 패턴

Factory 패턴(Factory Method Pattern)은 객체 생성 로직을 캡슐화하여, 객체를 직접 생성하지 않고, 이를 생성하는 메서드나 클래스를 통해 생성하는 디자인 패턴이다. 이 패턴은 객체 생성의 책임을 분리하고, 코드의 유연성과 확장성을 높이는 데 유용하다.


Factory 패턴의 핵심 개념

  1. 객체 생성 캡슐화

    • 클라이언트가 객체 생성 방식이나 구체적인 클래스를 알 필요 없이 객체를 생성할 수 있음
  2. 확장성 제공

    • 새로운 객체 생성 로직이 추가될 때 기존 코드를 수정하지 않고 새로운 Factory를 추가하면 됨
  3. 단일 책임 원칙(SRP)

    • 객체 생성 로직이 특정 Factory에 집중되어, 클래스의 역할이 명확히 구분됨

언제 사용해야 할까?

  1. 객체 생성 로직이 복잡할 때

    • 객체 생성 과정이 복잡하고 반복될 가능성이 많을 때
  2. 유연성이 필요한 경우

    • 특정 상황에 따라 다른 객체를 생성해야 하는 경우
  3. 클라이언트 코드와 객체 생성 로직을 분리하고 싶을 때

    • 클라이언트가 생성 방식에 대해 신경 쓰지 않도록 설계하고 싶을 때

구조

  1. Creator (Factory)

    • 객체 생성 메서드(factoryMethod)를 정의하는 추상 클래스 또는 인터페이스
    • 실제 객체 생성은 서브클래스에서 처리
  2. ConcreteCreator

    • Creator를 구현한 구체적인 클래스
    • 실제로 객체를 생성하는 역할을 담당
  3. Product

    • 생성되는 객체의 인터페이스 또는 부모 클래스
  4. ConcreteProduct

    • 실제로 생성되는 구체적인 객체

Swift로 구현하기

예제: 버튼(UIButton)을 생성하는 Factory

1️⃣ Product (UIButton)
import UIKit

// 기본 UIButton은 Product 역할
2️⃣ Factory Protocol 정의
protocol ButtonFactory {
    func createButton() -> UIButton
}
3️⃣ Concrete Factories 정의
class PrimaryButtonFactory: ButtonFactory {
    func createButton() -> UIButton {
        let button = UIButton(type: .system)
        button.setTitle("Primary", for: .normal)
        button.backgroundColor = .systemBlue
        button.setTitleColor(.white, for: .normal)
        button.layer.cornerRadius = 8
        return button
    }
}

class SecondaryButtonFactory: ButtonFactory {
    func createButton() -> UIButton {
        let button = UIButton(type: .system)
        button.setTitle("Secondary", for: .normal)
        button.backgroundColor = .lightGray
        button.setTitleColor(.black, for: .normal)
        button.layer.cornerRadius = 8
        return button
    }
}
4️⃣ 클라이언트 코드
func setupUI(buttonFactory: ButtonFactory) {
    let button = buttonFactory.createButton()
    button.frame = CGRect(x: 50, y: 100, width: 200, height: 50)
    view.addSubview(button)
}
사용 예시
let primaryFactory = PrimaryButtonFactory()
setupUI(buttonFactory: primaryFactory)

let secondaryFactory = SecondaryButtonFactory()
setupUI(buttonFactory: secondaryFactory)

Factory 패턴의 변형

1️⃣ Abstract Factory

  • 여러 관련 객체군을 생성하는 Factory를 추상화
  • 예: UIButton, UILabel, UITextField 등을 한 번에 생성
Abstract Factory Protocol
protocol ComponentFactory {
    func createButton() -> UIButton
    func createLabel() -> UILabel
}
Concrete Factory
class LightThemeFactory: ComponentFactory {
    func createButton() -> UIButton {
        let button = UIButton(type: .system)
        button.backgroundColor = .white
        button.setTitleColor(.black, for: .normal)
        return button
    }

    func createLabel() -> UILabel {
        let label = UILabel()
        label.textColor = .black
        return label
    }
}

class DarkThemeFactory: ComponentFactory {
    func createButton() -> UIButton {
        let button = UIButton(type: .system)
        button.backgroundColor = .black
        button.setTitleColor(.white, for: .normal)
        return button
    }

    func createLabel() -> UILabel {
        let label = UILabel()
        label.textColor = .white
        return label
    }
}
클라이언트 코드
func setupUI(factory: ComponentFactory) {
    let button = factory.createButton()
    let label = factory.createLabel()

    button.frame = CGRect(x: 50, y: 100, width: 200, height: 50)
    label.frame = CGRect(x: 50, y: 200, width: 200, height: 20)

    view.addSubview(button)
    view.addSubview(label)
}

let themeFactory: ComponentFactory = DarkThemeFactory()
setupUI(factory: themeFactory)

💡 장점

  1. 유연성: 클라이언트가 객체 생성 방식을 신경 쓰지 않아도 됨
  2. 확장성: 새로운 객체 유형을 쉽게 추가할 수 있음
  3. 코드 가독성: 객체 생성 로직이 한곳에 집중되어 관리하기 쉬움

⚠️ 단점

  1. 복잡성 증가: 작은 프로젝트에서 Factory를 도입하면 불필요하게 복잡해질 수 있음
  2. 추가 클래스 필요: 객체별로 별도의 Factory 클래스를 생성해야 하므로, 클래스 수가 늘어날 수 있음

📌 결론

Factory 패턴은 객체 생성 로직이 복잡하거나 객체 생성 방식이 자주 변경될 가능성이 있을 때 매우 유용하다.
다만, 단순한 프로젝트에서는 사용하지 않아도 충분하므로 필요할 때만 도입하는 것이 좋다.



클로저로 경량화한 Factory 구현

클로저를 사용한 경량화된 Factory 구현간단한 객체 생성 로직을 처리하기 위해 Factory 패턴의 개념을 최소한으로 적용하는 방식이다. 별도의 Factory 클래스를 만들지 않고, 클로저를 사용하여 객체 생성 책임을 전달하고 캡슐화한다.


핵심 개념

  1. 객체 생성 로직 캡슐화:

    • 클로저 내부에 객체 생성 로직을 정의하여, 사용하는 측에서 객체 생성 과정을 알 필요가 없게 만듦
    • 호출 시 필요한 객체를 반환
  2. 구체 클래스와의 결합 제거:

    • 클로저는 특정 타입(인터페이스 또는 프로토콜)을 반환하므로, 구체적인 구현에 의존하지 않음
  3. 간단하고 효율적:

    • 별도의 클래스를 만들 필요 없이 클로저 하나로 객체 생성 로직을 관리

구조

private let someFactory: () -> SomeProtocol
  • someFactory: 객체 생성 클로저로, SomeProtocol 타입을 반환
  • 이 클로저는 구체적인 객체 생성 로직을 캡슐화

장점

  1. 간단함:

    • 클래스를 추가로 만들지 않아 코드가 간결
  2. 유연성:

    • 클로저를 통해 생성 방식을 주입할 수 있어, 객체 생성 로직을 쉽게 변경 가능
  3. 테스트 가능성:

    • Mock 객체를 클로저로 주입해 테스트가 용이
  4. 의존성 주입과 잘 어울림:

    • 클로저로 객체 생성 책임을 주입받으면 확장성과 유지보수성이 높아짐

예제

1️⃣ 기본 구현

클로저 정의 및 사용
protocol DetailCoordinatorProtocol {
    func start()
}

class DetailCoordinator: DetailCoordinatorProtocol {
    func start() {
        print("DetailCoordinator started")
    }
}
MainCoordinator에서 사용
class MainCoordinator {
    private let detailCoordinatorFactory: () -> DetailCoordinatorProtocol

    init(detailCoordinatorFactory: @escaping () -> DetailCoordinatorProtocol) {
        self.detailCoordinatorFactory = detailCoordinatorFactory
    }

    func navigateToDetail() {
        let detailCoordinator = detailCoordinatorFactory()
        detailCoordinator.start()
    }
}
AppCoordinator에서 클로저 주입
let mainCoordinator = MainCoordinator(
    detailCoordinatorFactory: { DetailCoordinator() }
)

mainCoordinator.navigateToDetail()

2️⃣ 유닛 테스트

클로저를 사용하면 Mock 객체를 쉽게 주입할 수 있다:

Mock Coordinator
class MockDetailCoordinator: DetailCoordinatorProtocol {
    var didStart = false

    func start() {
        didStart = true
    }
}
테스트 코드
func testNavigateToDetail() {
    let mockCoordinator = MockDetailCoordinator()

    let mainCoordinator = MainCoordinator(
        detailCoordinatorFactory: { mockCoordinator }
    )

    mainCoordinator.navigateToDetail()

    XCTAssertTrue(mockCoordinator.didStart) // 테스트 성공 여부 확인
}

💡 언제 사용할까?

  1. 단순한 객체 생성 로직:

    • 생성 과정이 간단하고 복잡한 구성이 필요 없을 때
  2. 생성 책임 분리:

    • 객체 생성 로직을 클로저에 캡슐화하여 호출자가 구체적인 생성 과정을 알 필요 없게 할 때
  3. 테스트와 유연성 요구:

    • Mock 객체나 다른 생성 로직을 쉽게 주입해야 할 때

📌 결론

클로저를 활용한 경량화된 Factory 구현은 작고 단순한 요구사항에 적합한 방법이다.

  • Factory 클래스가 과한 느낌이 들거나,
  • 단순히 특정 객체를 생성하는 로직만 캡슐화하고 싶다면 클로저를 활용하는 것이 더 효율적이다.

이 방식은 테스트 가능성과 코드의 간결함을 동시에 확보하는 실용적인 방법이다!

profile
Reciprocity lies in knowing enough

0개의 댓글