추상 팩토리 패턴

기술블로그·2022년 8월 2일
0

Design Patterns

목록 보기
3/4

Refactoring Guru

문제

당신이 오늘의 집 서비스의 시스템 담당자라고 가정해 봅시다. 시스템 내부엔 위 그림과 같은 가구 클래스가 존재합니다.

다양한 가구 종류가 추가(의자, 소파 등)될 수도 있고, 다양한 컨셉(모던, 빅토리안 등)이 추가될 수 있습니다. 이 때 기존 코드를 수정하지 않고 새로운 항목을 추가하려면 어떻게 해야할까요? 문제를 재정의한다면 다음과 같습니다.

어떻게하면 코어 코드의 수정 없이 클래스를 쉽게 확장시킬 수 있을까?

해결

클라이언트가 인스턴스를 생성할 때 구현 클래스를 명시하지 않고 추상 클래스를 명시하여 생성하도록 해서 문제를 해결합니다. 이를통해 OCP, DIP를 달성합니다.

product는 위 예시에서의 가구에 해당합니다. ProductAChair, ProductBSofa.. 이런 식입니다.

factory는 가구를 생성하는 클래스입니다. 다양한 방식으로 가구를 생성할 수 있도록 해서 product가 컨셉별로 구분될 수 있도록 합니다. Factory1modern 컨셉의 가구 생성, Factory2victorian 컨셉의 가구 생성.. 이런 식입니다.

생각

클린코드의 저자는 시스템 생성과 시스템 사용을 분리하여 객체가 생성되는 시점을 애플리케이션을 신경쓰지 않는 것이 좋다고 합니다. (생성과 관련한 코드는 모두 main이나 main이 호출하는 모듈로 옮기고, 나머지 시스템은 모든 객체가 생성되었고 모든 의존성이 연결되었다고 가정하는 방식을 권장합니다.)

하지만 때로는 객체가 생성되는 시점을 애플리케이션이 결정할 필요가 생길 수 있는데, 이럴때 추상 팩토리 패턴을 사용하면 된다고 이해했습니다.

또한, 구현한 코드를 보면 DTO를 마치 클래스인 것 처럼 인터페이스를 통해 다루는데, 실제 상황에선 그럴 필요가 없습니다. 클라이언트가 구현 클래스가 아닌 추상 클래스를 의존하도록 해서 OCP와 DIP를 달성하는 것이 핵심이지, 너무 딱딱하게 클래스 다이어그램을 항상 100%따를 필요는 없다고 이해했습니다.

구현

파일구조

├── abstract_factory
   ├── furniture
   │   ├── furnitureFactory.go
   │   ├── artDecoFurnitureFactory.go
   │   ├── victorianFurnitureFactory.go
   │   ├── modernFurnitureFactory.go
   │   ├── chair.go
   │   ├── desk.go
   │   └── sofa.go
   ├── go.mod
   └── main.go

실행

cd abstract_factory && go mod init abstract_factory

furnitureFactory.go: Abstract Factory

package furniture

import "fmt"

type FurnitureFactory interface {
	CreateChair() IChair
	CreateSofa() ISofa
	CreateDesk() IDesk
}

func GetFurnitureFactory(concept string) (FurnitureFactory, error) {
	if concept == "art_deco" {
		return &ArtDecoFurnitureFactory{}, nil
	} else if concept == "victorian" {
		return &VictorianFurnitureFactory{}, nil
	} else if concept == "modern" {
		return &ModernFurnitureFactory{}, nil
	}

	return nil, fmt.Errorf("unknown furniture concept passed")
}

artDecoFurnitureFactory.go: Concrete Factory 1

package furniture

type ArtDecoFurnitureFactory struct {
}

func (f *ArtDecoFurnitureFactory) CreateChair() IChair {
	return &ArtDecoChair{
		Chair: Chair{
			style:  "Art Deco",
			legCnt: 0,
			weight: 10,
		},
	}
}

func (f *ArtDecoFurnitureFactory) CreateSofa() ISofa {
	return &ArtDecoSofa{
		Sofa: Sofa{
			style:  "Art Deco",
			cost:   100,
			weight: 50,
		},
	}
}

func (f *ArtDecoFurnitureFactory) CreateDesk() IDesk {
	return &ArtDecoDesk{
		Desk: Desk{
			style:  "Art Deco",
			legCnt: 2,
			cost:   100,
			weight: 100,
		},
	}
}

victorianFurnitureFactory.go: Concrete Factory 2

package furniture

type VictorianFurnitureFactory struct {
}

func (f *VictorianFurnitureFactory) CreateChair() IChair {
	return &ModernChair{
		Chair: Chair{
			style:  "Victorian",
			legCnt: 0,
			weight: 10,
		},
	}
}

func (f *VictorianFurnitureFactory) CreateSofa() ISofa {
	return &ModernSofa{
		Sofa: Sofa{
			style:  "Victorian",
			cost:   100,
			weight: 50,
		},
	}
}

func (f *VictorianFurnitureFactory) CreateDesk() IDesk {
	return &ModernDesk{
		Desk: Desk{
			style:  "Victorian",
			legCnt: 2,
			cost:   100,
			weight: 100,
		},
	}
}

modernFurnitureFactory.go: Concrete Factory 3

package furniture

type ModernFurnitureFactory struct {
}

func (f *ModernFurnitureFactory) CreateChair() IChair {
	return &ModernChair{
		Chair: Chair{
			style:  "modern",
			legCnt: 0,
			weight: 10,
		},
	}
}

func (f *ModernFurnitureFactory) CreateSofa() ISofa {
	return &ModernSofa{
		Sofa: Sofa{
			style:  "modern",
			cost:   100,
			weight: 50,
		},
	}
}

func (f *ModernFurnitureFactory) CreateDesk() IDesk {
	return &ModernDesk{
		Desk: Desk{
			style:  "modern",
			legCnt: 2,
			cost:   100,
			weight: 100,
		},
	}
}

chair.go : Abstract Product 1 & Concrete Product 1

예시에선 chair가 메서드가 없는 DTO 형태인데, 코드는 메서드가 존재하는 클래스에 대해서도 확장 가능하도록 구현됨

package furniture

type IChair interface {
	setChairStyle(style string) //억지메소드..
}

type Chair struct {
	style  string
	legCnt int
	weight int
}

func (c *Chair) setChairStyle(style string) {
	c.style = style
}

type ArtDecoChair struct {
	Chair
}

type VictorianChair struct {
	Chair
}

type ModernChair struct {
	Chair
}

sofa.go : Abstract Product 2 & Concrete Product 2

package furniture

type ISofa interface {
	setSofaStyle(style string) //억지메소드..
}

type Sofa struct {
	style  string
	cost   int
	weight int
}

func (s *Sofa) setSofaStyle(style string) {
	s.style = style
}

type ArtDecoSofa struct {
	Sofa
}

type VictoriaSofa struct {
	Sofa
}

type ModernSofa struct {
	Sofa
}

desk.go : Abstract Product 3 & Concrete Product 3

package furniture

type IDesk interface {
	setDeskStyle(style string) //억지메소드..
}

type Desk struct {
	style  string
	legCnt int
	cost   int
	weight int
}

func (d *Desk) setDeskStyle(style string) {
	d.style = style
}

type ArtDecoDesk struct {
	Desk
}

type VictorianDesk struct {
	Desk
}

type ModernDesk struct {
	Desk
}

main.go: Client

package main

import (
	"abstract_factory/furniture"
	"fmt"
)

func main() {
	artDecoFactory, _ := furniture.GetFurnitureFactory("art_deco")
	victorianFactory, _ := furniture.GetFurnitureFactory("victorian")
	modernFactory, _ := furniture.GetFurnitureFactory("modern")

	artDecoChair := artDecoFactory.CreateChair()
	artDecoSofa := artDecoFactory.CreateSofa()
	artDecoDesk := artDecoFactory.CreateDesk()

	victorianChair := victorianFactory.CreateChair()
	victorianSofa := victorianFactory.CreateSofa()
	victorianDesk := victorianFactory.CreateDesk()

	modernChair := modernFactory.CreateChair()
	modernSofa := modernFactory.CreateSofa()
	modernDesk := modernFactory.CreateDesk()

	fmt.Println(artDecoChair)
	fmt.Println(artDecoSofa)
	fmt.Println(artDecoDesk)

	fmt.Println(victorianChair)
	fmt.Println(victorianSofa)
	fmt.Println(victorianDesk)

	fmt.Println(modernChair)
	fmt.Println(modernSofa)
	fmt.Println(modernDesk)
}

수행결과

&{{Art Deco 0 10}}
&{{Art Deco 100 50}}
&{{Art Deco 2 100 100}}
&{{Victorian 0 10}}
&{{Victorian 100 50}}
&{{Victorian 2 100 100}}
&{{modern 0 10}}
&{{modern 100 50}}
&{{modern 2 100 100}}
profile
Software Engineer

0개의 댓글