👨🏻‍💻iOS 공부/iOS & Swift

[Design Pattern] Factory Pattern과 Abstract Factory Pattern

728x90
반응형

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)타고 가자!")
    }
}

보통은 이렇게까지 만들고 필요한 부분에서 CarBicycle을 초기화하여 인스턴스를 생성해서 사용했을 것이다.

만약 어떤 특정 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가 있는 셈이다.

 

앞선 예제를 변형해보면서 이해해보자.

 

차를 만드는 회사 중에 다들 알고 있을 만한 회사로 예시를 들어보자. BMWBenz가 있다고 해보자. 이 둘은 모두 이동수단을 만드는 역할을 한다.

 

만약에 이제부터 차 말고도 자전거도 같이 만들어야 한다고 하는 경우라고 가정해보자.

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

https://icksw.tistory.com/237

https://icksw.tistory.com/235

https://ko.wikipedia.org/wiki/%ED%8C%A9%ED%86%A0%EB%A6%AC_%EB%A9%94%EC%84%9C%EB%93%9C_%ED%8C%A8%ED%84%B4

 

 

728x90
반응형