[Swift] Factory 패턴

최근에 “쉽게 배워 바로 써먹는 디자인 패턴”이라고 책을 하나 사서 읽게 되었습니다. 24가지의 패턴에 대해 서술되어 있는데, 패턴 하나씩 포스트를 작성해볼려고 합니다. 24가지의 패턴이니까 24개의 포스트가 생기겠군요.

블로그를 운영하는 목적은 어디까지나 제가 몰랐던 것을 기록하기 위함이라서, 알고 있는 패턴들은 포스트를 작성하지 않을 계획입니다. (대충 알고 있는 것은 포스트 작성 예정)

디자인 패턴을 설명하기 위한 예시가 PHP로 설명되어 있어서, 어느 정도 차이점은 있지만 어차피 언어를 위한 책이 아니라 디자인 패턴을 설명하기 위한 책이기 때문에 크게 문제는 없어보입니다.

물론 저는 Swift로 예시를 들어서 기록할 생각입니다.

Factory 패턴이란?

Factory라는게 직역하면 공장이라는 말입니다. 정말 간단하게 말하자면, 객체의 생성을 다른 클래스에서 해준다고 생각하시면 됩니다. 그러면 여기서 의문이 생기죠! 왜 그렇게 해주는 것이지?

function 안에서 다른 class를 생성했다고 생각해봅시다.

class Good {
    
}

class Hello {
    func test() {
        let good = Good()
    }
}

예시를 보시면, Hello class의 test function에서 Good class를 생성하고 있는 예시입니다. 이렇게 되면 Hello class는 Good class에 의존성이 생깁니다. 이 의존성을 느슨하게 해주기 위해서 나온 디자인 패턴이 Factory 패턴이라고 합니다.

의존성을 느슨하게?

뭔가 이런 객체지향에 대해 학습을 하면서, 의존성 주입이나 의존성 역전은 들어봤어도 의존성을 느슨하게 한다는 말은 또 처음 들어보네요. 음.. 아직 뭐가 더 좋은지는 잘 모르겠습니다.

저의 개인적인 생각이지만, 제가 생각할 땐 느슨한 것보단 주입하거나 역전하는게 더 결합도를 낮게할 것이라고 생각이 듭니다.

만약에 의존성을 주입한다고 가정했을 때, function안에 function이 있고 이런식으로 쭉 타고 들어가다가 마지막 function에서만 특정 class를 사용하는 형태면 모든 function의 parameter에 주입해줘야합니다. 이럴 경우에는 Factory 패턴이 더 나을 수 있다는 생각이 듭니다.

자, 그럼 Factory 패턴을 활용해서 의존성을 느슨하게 하는 방법을 해봅시다.

class Factory {
    static func makeGood() -> Good {
        return Good()
    }
}

class Good {
    
}

class Hello {
    func test() {
        let good = Factory.makeGood()
    }
}

이런 식으로 특정 class를 생성해주는 function을 static으로 지정해서 class에 대한 생성을 직접해주지 않고 다른 class(Factory)가 위임받아서 처리해줍니다.

여기서 얻게되는 이점은 class 이름 변경에 용이하다는 장점이 있습니다.

Protocol 활용

protocol을 활용해서 구체타입이 아닌 추상타입으로 생성해줄 수 있습니다. 이렇게 처리할 경우, 다양한 상황에서 상황에 맞는 class를 생성할 수 있습니다.

class Factory {
    static func makeFeel(_ id: String) -> FeelProtocol? {
        if id == "Good" {
            return Good()
        } else if id == "Bad" {
            return Bad()
        }
        
        return nil
    }
}

protocol FeelProtocol {
    func sampleFunction()
}

class Good: FeelProtocol {
    func sampleFunction() {
        
    }
}

class Bad: FeelProtocol {
    func sampleFunction() {
        
    }
}

class Hello {
    func test() {
        let good = Factory.makeFeel("Good")
    }
}

id 값에 따라 다른 class를 생성하는 모습을 확인할 수 있습니다. Hello class에서는 구체타입을 모르기 때문에 더욱 안전하게 처리할 수 있다고 생각합니다.

책에서 PHP 예시는 return type이 정해져있지 않아서 다른 class를 return해도 문제가 없어보이지만, Swift는 정해져있기 때문에 protocol로 구체타입이 아닌 추상타입을 return해주는 방식으로 처리해줬습니다.

Protocol + Enum

바로 위에 protocol만 사용한 예시에서 id값이 String이기 때문에 return type을 optional로 처리해줬습니다. 생각해논 Good과 Bad 이외의 값이 들어오게 될 경우, 생성해야될 타입을 모르기 때문에 nil로 처리를 했습니다. 하지만 이런 예외상황을 방지하고 싶다면, enum을 사용해서 방지할 수 있습니다.

class Factory {
    static func makeFeel(_ id: FeelEnum) -> FeelProtocol {
        switch id {
        case .good:
            return Good()
        case .bad:
            return Bad()
        }
    }
}

enum FeelEnum {
    case bad
    case good
}

protocol FeelProtocol {
    func sampleFunction()
}

class Good: FeelProtocol {
    func sampleFunction() {
        
    }
}

class Bad: FeelProtocol {
    func sampleFunction() {
        
    }
}

class Hello {
    func test() {
        let good = Factory.makeFeel(.good)
    }
}

이렇게 enum을 사용할 경우, enum으로 정의된 case로만 생성이 가능하기 때문에 예외상황을 방지할 수 있습니다.

장점

  • 중복된 코드를 정리하는 효과
  • 클래스 이름 변경 용이
  • 어떤 객체를 생성할지 모르는 초기 단계 코드에 매우 유용

단점

  • 객체 생성을 위임하는 데 별도의 새로운 클래스 필요