import ( "코딩", "행복", "즐거움" )

플라이웨이트 패턴 본문

소프트웨어 아키텍처

플라이웨이트 패턴

더코드마니아 2023. 3. 9. 12:36

플라이웨이트 패턴은 객체를 공유하여 메모리 사용을 최적화하는 디자인 패턴 입니다!!!

 

일반적으로 많은 객체를 생성해야 하는 상황에서, 이들 객체는 많은 메모리를 사용할 수 있어요.

but!! 이들 객체 중에는 상태가 공유될 수 있는 부분이 있기 때문에, 이 부분을 공유하면서 객체의 생성 수를 줄일 수 있습니다. 이렇게 생성된 객체를 플라이웨이트 객체라고 부르죠!

 

플라이웨이트 패턴은 객체를 생성할 때 공유 가능한 부분과 개별적인 부분을 나누어 설계한답니당..

이를 위해 객체의 내부 상태를 분리하고, 내부 상태를 변경하지 않는 메소드를 사용하여 객체를 조작합니다.

 

이 패턴을 사용하면 객체 생성 수가 줄어들어 메모리 사용량이 감소하고, 객체 생성 시간도 줄어들어 프로그램의 실행 속도를 향상시킬 수 있습니다.

 

구조 

구조를 살펴볼께용!!

 

Flyweight(플라이웨이트) 인터페이스

객체를 공유하기 위한 인터페이스를 제공합니다. 이 인터페이스를 구현하는 클래스는 내부 상태를 가질 수 있습니다.

 

Concrete Flyweight(구체적인 플라이웨이트) 클래스

Flyweight 인터페이스를 구현하며, 공유 가능한 상태를 가지고 있습니다.

 

Flyweight Factory(플라이웨이트 팩토리) 클래스

Flyweight 객체를 생성하고 관리합니다. 생성된 Flyweight 객체는 캐시(cache)나 풀(pool)에 저장되어 재사용될 수 있습니다.

 

Client(클라이언트) 클래스

Flyweight 객체를 사용하는 클래스입니다. Client는 Flyweight 객체를 요청할 때 Flyweight Factory를 통해 객체를 얻습니다.

 

package main

import "fmt"

// Flyweight 인터페이스
type Flyweight interface {
    Operation()
}

// ConcreteFlyweight 클래스
type ConcreteFlyweight struct {
    state string
}

func (c *ConcreteFlyweight) Operation() {
    fmt.Printf("ConcreteFlyweight: %s\n", c.state)
}

// FlyweightFactory 클래스
type FlyweightFactory struct {
    flyweights map[string]Flyweight
}

func NewFlyweightFactory() *FlyweightFactory {
    return &FlyweightFactory{
        flyweights: make(map[string]Flyweight),
    }
}

func (f *FlyweightFactory) GetFlyweight(state string) Flyweight {
    if flyweight, ok := f.flyweights[state]; ok {
        return flyweight
    }
    flyweight := &ConcreteFlyweight{state: state}
    f.flyweights[state] = flyweight
    return flyweight
}

// Client 클래스
type Client struct {
    factory *FlyweightFactory
}

func (c *Client) Operation(states []string) {
    for _, state := range states {
        flyweight := c.factory.GetFlyweight(state)
        flyweight.Operation()
    }
}

func main() {
    factory := NewFlyweightFactory()

    // Client1은 "state1", "state2", "state3" 상태의 Flyweight 객체를 사용합니다.
    client1 := &Client{factory: factory}
    client1.Operation([]string{"state1", "state2", "state3"})

    // Client2은 "state1", "state4", "state5" 상태의 Flyweight 객체를 사용합니다.
    client2 := &Client{factory: factory}
    client2.Operation([]string{"state1", "state4", "state5"})
}

좀더 실제적인 예제를 작성 해보겠습니다. 

package main

import "fmt"

// Item 인터페이스
type Item interface {
	Use()
}

// ConcreteItem 클래스
type ConcreteItem struct {
	id   int
	name string
}

func (i *ConcreteItem) Use() {
	fmt.Printf("Use item with ID %d and name %s\n", i.id, i.name)
}

// ItemFactory 클래스
type ItemFactory struct {
	items map[int]Item
}

func NewItemFactory() *ItemFactory {
	return &ItemFactory{
		items: make(map[int]Item),
	}
}

func (f *ItemFactory) GetItem(id int, name string) Item {
	if item, ok := f.items[id]; ok {
		fmt.Printf("Get cached item with ID %d and name %s\n", id, name)
		return item
	}

	item := &ConcreteItem{id: id, name: name}
	fmt.Printf("Create new item with ID %d and name %s\n", id, name)
	f.items[id] = item
	return item
}

// Client 클래스
type Client struct {
	factory *ItemFactory
}

func (c *Client) UseItems(ids []int, names []string) {
	for i, id := range ids {
		item := c.factory.GetItem(id, names[i])
		item.Use()
	}
}

func main() {
	factory := NewItemFactory()
	client := &Client{factory: factory}

	// 1번 아이템과 2번 아이템을 사용합니다.
	client.UseItems([]int{1, 2}, []string{"Item A", "Item B"})

	// 1번 아이템과 3번 아이템을 사용합니다.
	client.UseItems([]int{1, 3}, []string{"Item A", "Item C"})
}

위의 예제에서는 Item 인터페이스와 ConcreteItem 클래스, ItemFactory 클래스, 그리고 Client 클래스를 구현했습니다.

ItemFactory 클래스는 Item 객체를 생성하고 관리하는 역할을 하며, Client 클래스는 Item 객체를 사용하는 역할을 합니다.

main 함수에서는 ItemFactory를 생성하고, Client를 생성하여 각각 다른 ID와 Name을 가지는 Item 객체를 사용하도록 하였습니다. Item 객체가 중복되지 않도록 ItemFactory 내부에 캐시(cache)를 구현하여 객체 생성을 최적화하였습니다.