home

추상 팩토리 패턴

  • 여러 스타일(또는 제품군)의 객체를 생성하는데, 각 스타일에 맞는 구체적인 클래스를 직접 코드에 작성하지 않고, 인터페이스를 통해 객체를 생성하게 한다.
  • 새로운 스타일이 추가되더라도 기존 코드를 수정할 필요 없이 새로운 팩토리 클래스를 추가하기만 하면 된다.
  • 단일 책임의 원칙과 개방/패쇄의 원칙을 지킬 수 있다.
  • 팩토리에서 생성되는 제품들의 호환성을 보장할 수 있다.

alt text 출처: refactoring.guru

  • Abstract Product: 개별 연관 제품들의 집합에 대한 인터페이스
  • Concrete Product: abstract product의 구현체
  • Abstract Factory: Concrete Product를 생성하기 위한 여러 메서드 집합 선언
  • Concrete Factory: 추상 팩토리의 생성 메서드들을 구현한다.

alt text

go 예시 코드

package main

import "fmt"

type Button interface {
    Paint()
}

type Checkbox interface {
    Paint()
}

type WinButton struct {}

func (b *WinButton) Paint() {
    fmt.Println("윈도우 버튼 칠하기")
}

type MacButton struct {}

func (b *MacButton) Paint() {
    fmt.Println("맥 버튼 칠하기")
}

type WinCheckbox struct {}

func (c *WinCheckbox) Paint() {
    fmt.Println("윈도우 체크박스 칠하기")
}

type MacCheckbox struct {}

func (c *MacCheckbox) Paint() {
    fmt.Println("맥 체크박스 칠하기")
}

type GUIFactory interface {
    CreateButton() Button
    CreateCheckbox() Checkbox
}

type WinFactory struct{}

func (f *WinFactory) CreateButton() Button {
    return &WinButton{}
}

func (f *WinFactory) CreateCheckbox() Checkbox {
    return &WinCheckbox{}
}

type MacFactory struct{}

func (f *MacFactory) CreateButton() Button {
    return &MacButton{}
}

func (f *MacFactory) CreateCheckbox() Checkbox {
    return &MacCheckbox{}
}

type Application struct {
    factory GUIFactory
    button Button
}

func (app *Application) CreateUI() {
    app.button = app.factory.CreateButton()
}

func (app *Application) Paint() {
    app.button.Paint()
}

func main() {
    var factory GUIFactory

    os := "Mac"
    if os == "Windows" {
        factory = &WinFactory{}
    } else if os == "Mac" {
        factory = &MacFactory{}
    } else {
        panic("알 수 없는 운영체제")
    }

    app := &Application{factory: factory}
    app.CreateUI()
    app.Paint()
}

언제 사용해야 하는가?

  • 시스템이 다양한 제품군을 지원해야 하지만 클라이언트 코드가 특정 제품군의 구체적인 클래스에 의존하지 않도록 하고 싶을 때
    • 새로운 제품군이 추가되어도 팩토리 클래스만 하나 추가하면 된다.
    • ex) 위의 예에서 리눅스에 지원해야 한다면? 밑과 같은 리눅스 팩토리만 생성해주면 된다.
      type LinuxFactory struct {}
    
      func (f *LinuxFactory) CreateButton() Button {
          return &LinuxButton{}
      }
    
      funx (f *LinuxFactory) CraeteCheckbox() Checkbox {
          return &LinuxCheckbox{}
      }
    
    • 즉 관련성 있는 여러 종류의 객체를 일관성 있게 생성해주고 싶을 때
  • 객체를 생성할 때 구현체 보다 추상 클래스 만을 이용해 객체를 생성하고자 할 때
  • 객체 생성 로직이 복잡하고 다양한 변형이 필요할 때

깨알 Go 상식

  • Go에서는 인터페이스를 명시적으로 상속하지 않지만, 인터페이스에 정의된 모든 메서드를 구현함으로써 그 인터페이스를 만족
  • 인터페이스 타입의 변수에 구현체를 할당하려면 해당 구조체가 인터페이스의 모든 메서드를 구현해야 한다.