본문 바로가기

프로그래밍/Design Pattern

생성 패턴 - 팩토리(Factory)

Factory는 공장을 뜻하는 영어 단어이다. 디자인 패턴 중 팩토리가 왜 팩토리라는 이름을 갖게 되었는지는 유추하면서 포스팅을 읽어 나가는 것도 좋을 것 같다. 

 

먼저 다음 코드를 보자. (클래스 별로 파일을 쪼개고 싶지만, 너무 짧아서 Language.swift에 함께 넣었다)

// Language.swift

enum LanguageType {
    case Ko;
    case En;
}

protocol Language {
    func hello() -> String
}

class Korean: Language {
    public func hello() -> String {
        return "안녕"
    }
}

class English: Language {
    public func hello() -> String {
        return "Hello"
    }
}

Language라는 프로토콜에는 hello라는 String을 반환하는 함수가 존재한다. 그리고 이러한 프로토콜을 따르는 클래스 2개(Korean과 English)를 선언해 주었다. 

 

// Hello.swift

class Hello {
    private var len: Language

    init(_ len: Language) {                 // 인스턴스 생성
        self.len = len
    }

    public func greeting(to: String = "") -> String {
        return len.hello() + ", \(to)."     // 인스턴스 샤옹
    }
}

다음은 앞에서 선언해 준 Language 타입의 속성과 그 속성에 따른 greeting 메서드를 묶은 클래스를 선언해 주었다. 

 

그리고 다음과 같이 사용할 수 있다.

// main.swift

// without Factory
var obj = HelloWithoutFactory(Korean())        // 인스턴스의 생성과 사용을 분리하지 않음
print(obj.greeting(to: "Taeminator"))          // 안녕, Taeminator.

obj = HelloWithoutFactory(English())           // 인스턴스의 생성과 사용을 분리하지 않음
print(obj.greeting(to: "Taeminator"))          // Hello, Taeminator.

실행해보면, print문의 오른쪽 주석과 같이 잘 출력된다. 

 

여기서 Korean과 English가 마음에 안 들어서 각각 Korea와 USA로 바꾸려고 한다.

(이름을 짓는 것은 항상 어려우므로 언제든지 일어날 수 있는 상황이다)

 

그럼 간단하게, Language.swift의 두 개의 클래스의 이름을 바꾸고, main.swift에서 인스턴스를 생성하는 부분의 이름을 바꾸면 그만이다. 

 

그렇다면, 인스턴스를 생성하는 부분이 10군데라면?  혹은 100 군데라면??  그리고 각각의 인스턴스를 생성하는 부분이 다른 파일에 존재한다면?? 클래스의 이름을 바꾸기 위해 많은 시간이 들 것이다. 뿐만 아니라, 해당 클래스의 이름은 언제든지 또 바뀔 수 있다. 

 

이렇듯, 인스턴스를 생성하는 곳과 사용하는 곳을 분리하지 않으면, 의존도가 높아져 번거로운 일이 생길 가능성이 있다. 이럴 때 사용할 수 있는 패턴이 바로 팩토리(Factory)이다. 다음과 같이 Hello 클래스를 수정하고 Fectory 클래스를 추가해 보자.

 

// Hello.swift

class Hello {
    public func greeting(_ type: LanguageType, to: String = "") -> String {
        return Factory.getInstance(type).hello() + ", \(to)."       // 인스턴스 사용
    }
}
// Factory.swift

// Factory 클래스를 통해 인스턴스 생성을 따로 분리시켜 의존성을 떨어 뜨림
class Factory {
    static public func getInstance(_ type: LanguageType) -> Language {      // 인스턴스 생성
        switch(type) {
        case .Ko:
            return Korean()
        case .En:
            return English()
        }
    }
}

변화가 느껴지는가? 팩토리는 기존에 Hello에서 하던 인스턴스를 생성하는 역할을 하고 있다. 

(여기서 생성 패턴의 의미를 되짚어 봐도 좋을 것 같다. 팩토리를 포함에 생성 패턴에 속하는 디자인 패턴은 전부 객체를 생성하는 것과 관련된 디자인 패턴이라는 사실을!)

 

그리고 다음과 같이 사용 가능하다. 

// main.swift

// with Factory
let obj = HelloWithFactory()
print(obj.greeting(.Ko, to: "Taeminator"))     // 인스턴스 사용
                                                // 안녕, Taeminator.
print(obj.greeting(.En, to: "Taeminator"))     // 인스턴스 사용
                                                // Hello, Taeminator.

이제 아까와 마찬가지로 Language.swift에서 Korean을 Korea로, English를 USA로 수정한다고 가정해 보자. 수정을 하면 당연히 에러가 발생하는데 그 에러는 다음과 같다. 

이제 인스턴스를 생성하는 Korean()과 English()를 각각 Korea()와 USA()로 수정해 주면 된다. 그럼 모든 게 잘 동작한다. 

 

이전 코드에서는 인스턴스를 생성(또는 사용 - 생성과 사용하는 곳이 동일하므로)하는 곳을 일일이 찾아가며 수정해 주어야 했지만, 이번 코드에서는 인스턴스를 생성하는 Factory 클래스만 수정해 주면 된다. 

 

이것이 팩토리이다. 

이제 팩토리의 의미를 되짚어 보자. 팩토리의 뜻이 공장을 생각해 보자. 공장에서는 물건을 찍어낸다. 즉 물건(인스턴스)을 생성한다. 이렇듯 팩토리는 인스턴스 생성을 위임하여 처리하기 때문에 이러한 이름이 붙여졌다. 

 

팩토리는 인스턴스 생성을 위임하기 때문에 유연한 프로그램을 짤 수 있는 것 외에 몇 가지 장점이 있다. 

  1. 유연성과 확장성 개선
    • 앞에서 설명
  2. 인스턴스의 사용과 생성을 분리를 통한 코드 정리
    • 지금은 코드의 양이 적지만, 규모가 커지면 사용과 생성을 명확히 구분하여 보다 깔끔한 코딩을 할 수 있게 된다. 
  3. 어떤 객체를 생성할지 모르는 초기 단계 코드에 유용
    • 앞에서 설명했듯이, 객체의 이름은 Factory에서 너무나 쉽게 변경이 가능하다. 일단 임의의 객체를 호출하여 사용한 뒤, 원할 때 바꿀 수 있다. 

팩토리는 추후 팩토리 메서드나 추상 팩토리로 확장되기도 하고, 다른 패턴과 결합하여 자주 사용되므로 잘 알아 둬야겠다. 

 

'프로그래밍 > Design Pattern' 카테고리의 다른 글

생성 패턴 - 싱글톤(Singleton) 1  (0) 2021.07.03
디자인 패턴(Design Pattern)  (0) 2021.07.01