ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Factory Pattern
    iOS 2025. 3. 3. 19:25

    안녕하세요. 그린입니다 🍏

    이번 포스팅에서는 Factory Pattern에 대해 iOS를 기준으로 간단히 정리해보려 합니다 🙋🏻


    Factory Pattern?

    Factory Pattern은 객체 생성을 중앙 집중화해 관리하는 Creational(생성) 디자인 패턴입니다.

    Factory Pattern을 통해서 객체 생성 로직을 한 곳에 모을 수 있고, 변경이 필요할 때 최소한의 수정으로 유지보수 할 수 있다는 장점이 있죠!

     

    Factory Pattern의 핵심 개념은 몇가지가 있어요 😃

     

    1️⃣ 객체 생성을 캡슐화해 클라이언트 코드에서 객체 생성 로직을 숨김

    2️⃣ 객체의 구체적인 구현을 숨기고 인터페이스 (Protocol)을 통해 사용

    3️⃣ 객체 생성 방식을 변경해도 클라이언트 코드에 영향을 주지 않음

     

    그럼 왜 Factory Pattern이 필요한지 알아볼까요?

     


    Why do you need Factory Pattern?

    아래 코드를 한번 볼까요?

     

    class UserService {
        func fetchUser() -> String {
            return "Green"
        }
    }
    
    class ViewController {
        let userService = UserService()
        
        func getUserData() {
            print(userService.fetchUser())
        }
    }

     

    해당 코드를 보면 일반적이지만, VC가 UserService의 구체적인 구현을 직접적으로 알고 있습니다.

    즉, 의존성이 높아지며 유지보수에 난항을 겪을 수 있어요.

     

    또한, 여러곳에서 UserService를 직접 생성하면 코드가 중복되고, 생성 방식이 바뀔 경우에 모든 사용된 곳에서 코드를 수정해줘야 하죠.

     

    그리고, 테스트 코드 작성이 어렵습니다.

    유닛 테스트에서 MockUserService를 사용하려고 해도 직접 생성하는 구조라서 변경이 까다롭습니다.

     

    그럼 이걸 Factory Pattern으로 해결해볼까요?

     


    Factory Pattern의 타입과 구현

    Simple Factory

    단순하게 Factory 클래스를 만들어 객체 생성을 관리하는 방식

     

    protocol UserService {
        func fetchUser() -> String
    }
    
    class DefaultUserService: UserService {
        func fetchUser() -> String {
            return "Green"
        }
    }
    
    class MockUserService: UserService {
        func fetchUser() -> String {
            return "Mock User"
        }
    }
    
    // ✅ Factory 클래스
    class UserServiceFactory {
        static func create(isTestMode: Bool) -> UserService {
            return isTestMode ? MockUserService() : DefaultUserService()
        }
    }
    
    // ✅ Factory 사용
    class ViewController {
        let userService: UserService
        
        init() {
            self.userService = UserServiceFactory.create(isTestMode: false)
        }
        
        func getUserData() {
            print(userService.fetchUser())
        }
    }

     

    이렇게 코드가 구현될 수 있어요.

    UserService의 구체적인 구현을 VC가 알 필요가 없게 되죠.

    또한, MockUserService로 쉽게 교체가 가능하기에 테스트에 용이합니다.

     


    Factory Method Pattern

    객체 생성을 위한 메서드를 인터페이스화하고, 서브클래스에서 실제 객체를 생성하도록 위임하는 방식입니다.

     

    protocol UserService {
        func fetchUser() -> String
    }
    
    class DefaultUserService: UserService {
        func fetchUser() -> String {
            return "Green"
        }
    }
    
    class MockUserService: UserService {
        func fetchUser() -> String {
            return "Mock User"
        }
    }
    
    // 🎯 Factory Method 패턴
    protocol UserServiceFactory {
        func createUserService() -> UserService
    }
    
    class ProductionUserServiceFactory: UserServiceFactory {
        func createUserService() -> UserService {
            return DefaultUserService()
        }
    }
    
    class TestUserServiceFactory: UserServiceFactory {
        func createUserService() -> UserService {
            return MockUserService()
        }
    }
    
    // ✅ Factory 사용
    class ViewController {
        let userService: UserService
        
        init(factory: UserServiceFactory) {
            self.userService = factory.createUserService()
        }
        
        func getUserData() {
            print(userService.fetchUser())
        }
    }
    
    // 실제 앱에서 사용
    let productionVC = ViewController(factory: ProductionUserServiceFactory())
    productionVC.getUserData() // "Green"
    
    // 테스트 환경에서 사용
    let testVC = ViewController(factory: TestUserServiceFactory())
    testVC.getUserData() // "Mock User"

     

    장점으로는 객체 생성 방식을 서브클래스에서 정의가 가능하기에 유연한 확장이 가능합니다.

    또한, 마찬가지로 테스트 환경에서 다른 객체를 쉽게 주입 가능해요.

     


    Abstract Factory Pattern

    여러 개의 관련된 객체를 그룹화해 생성하는 패턴입니다.

     

    protocol UserService {
        func fetchUser() -> String
    }
    
    protocol AuthService {
        func authenticate() -> Bool
    }
    
    // ✅ 각 서비스의 구현체
    class DefaultUserService: UserService {
        func fetchUser() -> String { return "Green" }
    }
    
    class DefaultAuthService: AuthService {
        func authenticate() -> Bool { return true }
    }
    
    // 🎯 Abstract Factory
    protocol ServiceFactory {
        func createUserService() -> UserService
        func createAuthService() -> AuthService
    }
    
    // ✅ Production 환경에서 사용할 팩토리
    class ProductionServiceFactory: ServiceFactory {
        func createUserService() -> UserService { return DefaultUserService() }
        func createAuthService() -> AuthService { return DefaultAuthService() }
    }
    
    // ✅ 팩토리를 이용한 객체 생성
    class ViewController {
        let userService: UserService
        let authService: AuthService
        
        init(factory: ServiceFactory) {
            self.userService = factory.createUserService()
            self.authService = factory.createAuthService()
        }
        
        func performLogin() {
            if authService.authenticate() {
                print(userService.fetchUser())
            }
        }
    }
    
    let factory = ProductionServiceFactory()
    let vc = ViewController(factory: factory)
    vc.performLogin() // "Green"

     

    연관된 객체를 하나의 팩토리에서 관리가 가능하기에 일관성을 유지시켜 줄 수 있어요.

    또한, 서브클래스를 활용해서 쉽게 확장도 가능합니다.

     

    이 Factory Pattern은 DI와 연관을 지을 수 있어요 😃

     


    Factory Pattern & DI (Dependency Injection)

    Factory Pattern을 DI 컨테이너와 함께 사용하면 더욱 유용합니다.

     

    class Container {
        static let shared = Container()
        
        private var factories: [String: () -> Any] = [:]
        
        func register<T>(type: T.Type, factory: @escaping () -> T) {
            factories[String(describing: type)] = factory
        }
        
        func resolve<T>(type: T.Type) -> T {
            return factories[String(describing: type)]!() as! T
        }
    }
    
    // 의존성 등록
    Container.shared.register(type: UserService.self) { DefaultUserService() }
    
    // 의존성 주입
    class ViewController {
        let userService: UserService
        
        init() {
            self.userService = Container.shared.resolve(type: UserService.self)
        }
        
        func getUserData() {
            print(userService.fetchUser())
        }
    }

     

    Factory Pattern과 DI 컨테이너를 결합해 객체 생성 로직을 완전히 분리할 수 있죠.

    의존성 관리가 편리해지고 다양한 구현체를 쉽게 변경할 수 있죠.


    마무리

    Factory Pattern에 대해 알아봤는데요.

     

    한번 정리해볼까요?

     

    Factory Pattern을 사용하면 아래와 같은 이점을 가져갈 수 있어요.

     

    1️⃣ 객체 생성을 캡슐화해 유지보수성을 높여줌

    2️⃣ 코드 결합도를 낮춰 확장성 높임

    3️⃣ Mock 객체를 활용해 유닛 테스트 용이

     

    그럼 어떤 Factory Pattern을 사용할까요?

     

    1️⃣ Simple Factory - 가장 간단한 형태로 작은 프로젝트에 적합

    2️⃣ Factory Method - 객체 생성을 하위 클래스에서 정의할 수 있어 확장성이 높음

    3️⃣ Abstract Factory - 여러 개의 관련 객체를 생성해야 할 때 적합

     

    즉, Factory Pattern을 적용하면 유지보수가 쉽고 확장 가능한 iOS 앱을 만들 수 있습니다 🔥

    'iOS' 카테고리의 다른 글

    iOS에서 서버 과부하 감지 및 API 호출 최적화  (0) 2025.03.15
    Server-Driven UI  (0) 2025.03.07
    카카오톡 공유하기 (메시지 템플릿)  (36) 2024.12.05
    Bring your app to Siri (feat. WWDC 2024)  (7) 2024.10.07
    Genmoji (feat. WWDC 2024)  (4) 2024.10.04
Designed by Tistory.