Swift/Design_Pattern

[Swift 디자인 패턴] Abstract Factory Pattern (추상 팩토리) - 디자인 패턴 공부 2

Dev_Pingu 2021. 5. 8. 23:18
반응형

안녕하세요 Pingu입니다.🐧

 

지난 글에서는 생성 패턴이 뭔지 알아봤었는데요, 이번 글에서는 생성 패턴 중에서 Abstract Factory Pattern(추상 팩토리 패턴)에 대해 공부해보려고 합니다. 추상 팩토리 패턴은 Kit 패턴이라고도 알려져 있다고 해요.

 

추상 팩토리 패턴의 목적

추상 팩토리 패턴의 목적은 구체적인 클래스를 지정하지 않고 관련된 객체들을 모으기 위한 인터페이스를 제공하는 것입니다. 또한 코드를 변경하지 않고도 조건에 따른 적절한 객체를 사용할 수 있게 해줍니다.

등장 배경

UI를 만들 때 스크롤 바, 버튼과 같은 뷰를 사용해서 만들게 되는데, 이들은 모두 다른 모양과 동작을 합니다. 이들은 모양과 동작은 다르지만 모두들 관련이 되어있다고 할 수 있습니다. 이러한 객체들을 프로그램 내에서 모두 각자 다른 객체로 만들게 되면 나중에 수정이 어려울 수 있기 때문에 이러한 객체들을 표현하는 하나의 추상 클래스를 정의하여 이를 해결하기 위한 방법으로 만들어졌습니다.

개념

추상 팩토리 패턴은 구체적인 클래스를 지정하지 않고 공통된 요소들을 갖는 개별 팩토리 그룹을 캡슐화하는 방법을 제공합니다.

 

Swift를 예로 들면 Protocol을 예로 들 수 있는데요, Protocol은 어떤 메서드나 변수를 가질 것이다라는 껍데기만 존재합니다. Protocol을 채택한 클래스나 구조체는 해당 Protocol이 정의한 껍데기 부분을 구체적으로 구현하고 나머지 더 필요한 부분을 추가하게 됩니다. 여기서 Protocol을 추상 팩토리라고 할 수 있습니다. 실제로 Protocol을 채택한 클래스를 사용할 땐 해당 클래스의 요소들만 사용하기 때문에 내부적으로 어떤 Protocol을 채택했는지 알 필요가 없습니다.

 

따라서 추상 팩토리 패턴은 껍데기만 정의해두고 관련 객체들에서 껍데기의 세부사항을 구현하기 때문에 객체 구성에 의존한다고 할 수 있습니다. Swift를 다시 예로 들면 추상 팩토리는 Protocol, 팩토리는 이를 채택한 클래스나 구조체가 됩니다.

 

추상 팩토리 패턴을 사용하면 런타임에서 이를 사용하는 코드를 변경하지 않고도 구체적인 구현을 바꿀 수 있습니다. 하지만 추상 팩토리 패턴을 사용하게되면 복잡성과 추가 작업은 발생할 수 있겠죠?

 

그럼 이러한 추상 팩토리가 사용될 수 있는 경우를 살펴보겠습니다.

  • 앱이 객체의 생성, 구성, 표현 방법과 독립적이어야 할 때
  • 앱이 여러 객체 중 하나로 구성되어야 할 때
  • 관련 객체들이 함께 사용할 수 있게 설계 할 때
  • 앱의 클래스 라이브러리를 제공하고 구현이 아닌 인터페이스만 표시할 때

예제

그럼 제가 이해한 내용을 바탕으로 간단하게 추상 팩토리 패턴을 Swift로 구현해보며 이해를 해볼게요.

혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다.

 

추상 팩토리 패턴을 공부할 때 많이 보이던 예가 GUI를 구성하는 예제였는데요, 저도 그 예제를 한 번 사용해보려고 해요.

일단 추상 팩토리 부분을 만들어줍니다.

protocol ViewAbstractFactory {
    public func createButton() -> Button
    public func createLabel() -> Label
}

protocol Button {
    func click()
}

protocol Label {
    var text: String
}

위와 같이 추상 팩토리 역할을 하는 코드를 만들게 되면 ViewAbstractFactory 프로토콜을 채택한 것들은 Button 프로토콜을 채택한 객체를 반환하는 createButton(), Label 프로토콜을 채택한 객체를 반환하는 createLabel() 메서드를 가져야 한다는 뜻이 됩니다.

 

버튼과 레이블을 사용하는 플랫폼은 다양하게 있겠지만 저는 Mac, Window, Linux에서 사용할 코드만 작성해볼게요.

만약 맥에서 사용할 버튼과 레이블을 만든다면 아래와 같이 만들면 되겠죠?

// Mac
class MacViewFactory: ViewAbstractFactory {
    func createButton() -> Button {
        return MacButton()
    }
    
    func createLabel() -> Label {
        return MacLabel(text: "Mac Label")
    }
}

class MacButton: Button {
    func click() {
        print("Mac Button Click!")
    }
}

class MacLabel: Label {
    var text: String
    init(text: String) {
        self.text = text
    }
}

동일한 방법으로 Window, Linux의 버튼, 레이블도 만들어 줍니다.

// Linux
class LinuxViewFactory: ViewAbstractFactory {
    func createButton() -> Button {
        return LinuxButton()
    }
    func createLabel() -> Label {
        return LinuxLabel(text: "Linux Label")
    }
}

class LinuxButton: Button {
    func click() {
        print("Linux Button Click")
    }
}

class LinuxLabel: Label {
    var text: String
    init(text: String) {
        self.text = text
    }
}
// Window
class WindowViewFactory: ViewAbstractFactory {
    func createButton() -> Button {
        return WindowButton()
    }
    
    func createLabel() -> Label {
        return WindowLabel(text: "Window Label")
    }
}

class WindowButton: Button {
    func click() {
        print("Window Button Click!")
    }
}

class WindowLabel: Label {
    var text: String
    init(text: String) {
        self.text = text
    }
}

 

자 그럼 이렇게 만든 3가지 플랫폼의 버튼, 레이블을 사용하려면 어떻게 하면 될까요?

이렇게 간단하게 사용할 수 있습니다.

 

만약 Linux, Window에서 사용할 버튼, 레이블을 만들고 싶다면 factory라는 이름의 객체에 각각의 팩토리 클래스로 인스턴스화 해주면 되겠죠?

 

또한 위와 같이 직접 Mac이냐 Window냐 Linux냐를 신경 쓰지 않아도 알맞은 factory 객체를 만들 수 있도록 ViewFactory 객체도 하나 만들어줘 볼게요.

위와 같이 factory 객체를 만들 때 OS에 맞는 객체를 사용할 수 있도록 만들면 됩니다.

현재 저는 Mac OS를 사용하고 있어서 Mac 버튼과 Mac 레이블이 생성된 것을 볼 수 있습니다.

Linux나 window 환경에서는 거기에 맞는 버튼과 레이블이 생성되겠죠?

 

이렇게 다양한 요소 별로 객체의 집합을 생성해야 할 때 추상 팩토리 패턴이 유용한 것을 볼 수 있습니다.

전체 코드는 여기서 볼 수 있습니다.

 

감사합니다.

반응형