맨위로가기

플라이웨이트 패턴

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

플라이웨이트 패턴은 객체 지향 프로그래밍에서 사용되는 디자인 패턴으로, 대량의 객체를 효율적으로 처리하기 위해 메모리 사용을 최적화한다. 이 패턴은 객체의 불필요한 중복을 줄이고 공유된 객체를 사용하여 메모리 사용량을 감소시키며, 객체의 고유한 상태와 외부 상태를 분리하여 공유 객체의 재사용을 가능하게 한다. 플라이웨이트 패턴은 가변성, 검색, 캐싱, 동시성 등의 구현 방식을 가지며, 팩토리 클래스를 통해 객체를 생성하고 관리한다.

더 읽어볼만한 페이지

  • 소프트웨어 최적화 - 성능 공학
    성능 공학은 시스템의 비즈니스 수익 증대를 위해 정해진 시간 안에 트랜잭션을 처리하도록 보장하고, 시스템 개발 실패 및 유지보수 비용 증가를 방지하며, 성능 관리와 모니터링을 통해 서비스 수준 계약을 준수하도록 한다.
  • 소프트웨어 최적화 - 프로파일링 (컴퓨터 프로그래밍)
    프로파일링(컴퓨터 프로그래밍)은 프로그램의 성능 분석 및 개선을 위한 기술로, 실행 시간 측정과 병목 현상 파악에 사용되며, 다양한 종류의 프로파일러가 존재한다.
  • 소프트웨어 디자인 패턴 - 모델-뷰-컨트롤러
    모델-뷰-컨트롤러(MVC)는 소프트웨어 디자인 패턴으로, 응용 프로그램을 모델, 뷰, 컨트롤러 세 가지 요소로 분리하여 개발하며, 사용자 인터페이스 개발에서 데이터, 표현 방식, 사용자 입력 처리를 분리해 유지보수성과 확장성을 높이는 데 기여한다.
  • 소프트웨어 디자인 패턴 - 스케줄링 (컴퓨팅)
    스케줄링은 운영 체제가 시스템의 목적과 환경에 맞춰 작업을 관리하는 기법으로, 장기, 중기, 단기 스케줄러를 통해 프로세스를 선택하며, CPU 사용률, 처리량 등을 기준으로 평가하고, FCFS, SJF, RR 등의 알고리즘을 사용한다.
플라이웨이트 패턴
개요
유형구조적 디자인 패턴
목적메모리 사용량 또는 개체 생성 비용을 줄이기 위해 많은 유사한 개체를 효율적으로 지원
동기애플리케이션에서 너무 많은 개체를 사용하면 비용이 많이 들 수 있음.
플라이웨이트 디자인 패턴은 여러 가상 개체가 상태의 일부를 공유할 수 있도록 하여 개체 구조를 변경하는 방법을 설명
활용개체 지향 언어에서 문자열을 사용
문서 편집기 및 그래픽 프로그램에서 문자
게임에서 개별 캐릭터 또는 트리
자바 가상 머신 (JVM) 내부
정의
의도세분화된 객체를 효율적으로 대량으로 지원하기 위해 공유를 사용
동기 부여그래픽 응용 프로그램에서 텍스트 문서를 효율적으로 표현하고 조작하는 방법을 고려
각 문자를 나타내는 데 객체를 사용하면 모든 문자가 자체 글꼴, 자간, 색상 등을 가지는 것이 가능
그러나 문서 내의 각 문자에 대해 객체를 가지는 것은 상당한 저장 공간 비용을 초래
플라이웨이트 패턴은 객체를 공유함으로써 이러한 문제를 해결
각 문자에 대해 객체를 생성하는 대신, 플라이웨이트 패턴은 공유 가능한 객체 (여기서는 문자)를 생성하고, 이 객체를 여러 개의 "가상" 객체에서 공유하도록 함
문맥에 따라 달라지는 정보 (예: 문자 위치, 글꼴 크기)는 공유 객체에 저장되지 않음
대신, 이 정보는 클라이언트 (예: 문서 편집기)에 의해 유지되고, 필요에 따라 공유 객체에 전달
적용 가능성다음과 같은 경우 플라이웨이트 패턴을 사용
응용 프로그램이 많은 수의 객체를 사용해야 하는 경우
저장 비용이 높고 객체 ID로 인한 오버헤드가 허용 불가능할 정도로 높은 경우
객체의 대부분의 상태를 외부화할 수 있는 경우
객체 그룹을 제거하고 참조로 대체할 수 있는 경우
응용 프로그램이 객체의 ID에 의존하지 않는 경우
플라이웨이트 디자인 패턴 UML 다이어그램
플라이웨이트 디자인 패턴 UML 다이어그램
참가자Flyweight (플라이웨이트)
플라이웨이트 객체의 인터페이스를 선언
상태가 저장되어야 한다면, 공유될 수 있는 상태를 받거나 찾을 수 있음
ConcreteFlyweight (구체적인 플라이웨이트)
Flyweight 인터페이스를 구현하고 상태를 저장
모든 공유 플라이웨이트 객체에 대한 상태를 나타냄
UnsharedConcreteFlyweight (공유되지 않는 구체적인 플라이웨이트)
Flyweight 인터페이스를 구현하고 상태를 저장
공유되지 않는 플라이웨이트 객체에 대한 상태를 나타냄
FlyweightFactory (플라이웨이트 팩토리)
기존 플라이웨이트 객체를 관리하고, 클라이언트가 요청할 때 플라이웨이트 객체를 생성
Client (클라이언트)
플라이웨이트 객체를 생성하고, 플라이웨이트 객체에 필요한 상태를 전달
협업클라이언트는 플라이웨이트 팩토리에 플라이웨이트 객체를 요청
플라이웨이트 팩토리는 기존 플라이웨이트 객체를 반환하거나, 새로운 플라이웨이트 객체를 생성하여 반환
클라이언트는 플라이웨이트 객체를 사용하여 작업을 수행
결과플라이웨이트 패턴은 다음과 같은 결과를 가져옴
저장 공간 절약: 플라이웨이트 패턴은 객체를 공유함으로써 저장 공간을 절약할 수 있음
객체 생성 비용 절감: 플라이웨이트 패턴은 객체를 공유함으로써 객체 생성 비용을 절감할 수 있음
복잡성 증가: 플라이웨이트 패턴은 객체를 공유함으로써 복잡성을 증가시킬 수 있음
구현플라이웨이트 패턴을 구현할 때는 다음과 같은 사항을 고려해야 함
공유 가능한 상태와 공유되지 않는 상태를 구분
공유 가능한 상태는 플라이웨이트 객체에 저장하고, 공유되지 않는 상태는 클라이언트에 저장
플라이웨이트 팩토리를 사용하여 플라이웨이트 객체를 생성
샘플 코드자바 샘플 코드 (영문 위키백과)
알려진 사용법플라이웨이트 패턴은 다음과 같은 곳에서 사용됨
문자열 풀링
데이터베이스 연결 풀링
객체 풀링
관련 패턴플라이웨이트 패턴은 다음과 같은 패턴과 관련됨
추상 팩토리 패턴
복합체 패턴
공유자원 패턴
장점
객체 수를 줄임플라이웨이트 패턴의 주요 장점은 응용 프로그램에서 필요한 객체 수를 줄이는 것
응용 프로그램에서 많은 수의 객체를 사용해야 하는 경우, 플라이웨이트 패턴을 사용하여 객체 수를 줄일 수 있음
이를 통해 메모리 사용량을 줄이고, 객체 생성 비용을 절감할 수 있음
메모리 사용량을 줄임플라이웨이트 패턴은 객체를 공유함으로써 메모리 사용량을 줄일 수 있음
응용 프로그램에서 많은 수의 객체를 사용해야 하는 경우, 플라이웨이트 패턴을 사용하여 메모리 사용량을 줄일 수 있음
이를 통해 응용 프로그램의 성능을 향상시킬 수 있음
단점
복잡성 증가플라이웨이트 패턴은 객체를 공유함으로써 복잡성을 증가시킬 수 있음
응용 프로그램에서 플라이웨이트 패턴을 사용해야 하는 경우, 플라이웨이트 패턴의 복잡성을 고려해야 함
플라이웨이트 패턴을 잘못 사용하면 응용 프로그램의 성능이 저하될 수 있음
상태 관리의 어려움플라이웨이트 패턴은 객체를 공유하므로, 상태 관리가 어려울 수 있음
응용 프로그램에서 플라이웨이트 패턴을 사용해야 하는 경우, 상태 관리를 신중하게 고려해야 함
플라이웨이트 패턴을 잘못 사용하면 응용 프로그램의 상태가 일관성을 잃을 수 있음
같이 보기
관련 디자인 패턴추상 팩토리 패턴
빌더 패턴
팩토리 메서드 패턴
싱글톤 패턴

2. 역사적 배경

플라이웨이트 패턴은 개별적으로 내장될 경우 많은 양의 메모리를 사용할 수 있는, 단순하고 반복적인 요소를 공유하는 대량의 객체를 처리할 때 유용하다. 공유 데이터를 외부 자료 구조에 보관하고 객체가 사용될 때 임시로 전달하는 것이 일반적이다.[5]

고전적인 예는 워드 프로세서에서 문자를 나타내는 데 사용되는 데이터 구조이다. 단순하게 접근하면, 문서의 각 문자는 글꼴 윤곽선, 글꼴 메트릭 및 기타 서식 데이터를 포함하는 글리프 객체를 가질 수 있다. 그러나 이렇게 하면 각 문자에 대해 수백 또는 수천 바이트의 메모리가 사용된다. 대신, 각 문자는 문서에서 동일한 문자의 모든 인스턴스에서 공유되는 글리프 객체에 대한 참조를 가질 수 있다. 이렇게 하면 각 문자의 위치만 내부적으로 저장하면 된다.[5]

결과적으로 플라이웨이트 객체는 다음을 수행할 수 있다.[5]


  • 불변적이고, 컨텍스트에 독립적이며, 공유 가능한 ''고유'' 상태(예: 지정된 문자 집합에서 문자 'A'의 코드)를 저장한다.
  • 변동적이고, 컨텍스트에 종속적이며, 공유할 수 없는 ''외부'' 상태(예: 텍스트 문서에서 문자 'A'의 위치)를 전달하기 위한 인터페이스를 제공한다.


클라이언트는 Flyweight 객체를 재사용하고 필요에 따라 외부 상태를 전달하여 물리적으로 생성되는 객체의 수를 줄일 수 있다.[5]

3. 구조

플라이웨이트 객체는 불변적이고, 문맥에 독립적이며, 공유 가능한 ''고유'' 상태(예: 지정된 문자 집합에서 문자 'A'의 코드)를 저장한다. 또한, 변동적이고, 문맥에 종속적이며, 공유할 수 없는 ''외부'' 상태(예: 텍스트 문서에서 문자 'A'의 위치)를 전달하기 위한 인터페이스를 제공한다.[5] 클라이언트는 플라이웨이트 객체를 재사용하고 필요에 따라 외부 상태를 전달하여 물리적으로 생성되는 객체의 수를 줄일 수 있다.

플라이웨이트 패턴은 불변 클래스를 다루는 경우에 전형적으로 사용된다. 불변 클래스는 인스턴스가 생성된 후에 해당 인스턴스의 상태가 변하지 않는 클래스이다.

3. 1. UML 클래스 다이어그램



UML 클래스 다이어그램은 다음을 보여준다.

  • `Client` 클래스: 플라이웨이트 패턴을 사용한다.
  • `FlyweightFactory` 클래스: `Flyweight` 객체를 생성하고 공유한다.
  • `Flyweight` 인터페이스: 외부 상태를 받아 연산을 수행한다.
  • `Flyweight1` 클래스: `Flyweight`를 구현하고 고유 상태를 저장한다.


3. 2. 런타임 상호 작용



시퀀스 다이어그램은 다음과 같은 런타임 상호 작용을 보여준다.

# `Client` 객체는 `FlyweightFactory`에서 `getFlyweight(key)`를 호출하여 `Flyweight1` 객체를 반환받는다.

# 반환된 `Flyweight1` 객체에서 `operation(extrinsicState)`을 호출한 후, `Client`는 다시 `FlyweightFactory`에서 `getFlyweight(key)`를 호출한다.

# `FlyweightFactory`는 이미 존재하는 `Flyweight1` 객체를 반환한다.

플라이웨이트 패턴으로 설계된 API에서 사용자는 `Flyweight` 클래스에 해당하는 인스턴스를 얻을 때, 직접 해당 클래스의 생성자를 호출하는 대신 `FlyweightFactory.getFlyweight()`에 접근한다.

호출된 `FlyweightFactory` 객체는 상황에 따라 다음과 같이 동작한다.

  • 해당 시점에서 대상 인스턴스가 생성되지 않은 경우:

## 대상 인스턴스를 새로 생성한다.

## 생성한 인스턴스를 풀링한다(다시 말해, 멤버의 컨테이너 객체에 저장한다).

## 생성된 인스턴스를 반환한다.

  • 대상 인스턴스가 이미 생성된 경우:

## 대상 인스턴스를 풀에서 호출한다.

## 대상 인스턴스를 반환한다.

이처럼 `FlyweightFactory`에서는 상황에 따라 다른 처리가 이루어지지만, 사용자 측에서 얻는 결과는 완전히 동일하므로, 사용자는 `FlyweightFactory`의 내부 구조를 의식하지 않고 사용할 수 있다는 점이 특징이다.

4. 구현

플라이웨이트 패턴을 구현하는 방법에는 여러 가지가 있다. 한 가지 예시는 가변성, 즉 외생 플라이웨이트 상태를 저장하는 객체가 변경될 수 있는지 여부이다.

불변 객체는 쉽게 공유되지만, 상태가 변경될 때마다 새로운 외생 객체를 생성해야 한다. 반대로, 가변 객체는 상태를 공유할 수 있다. 가변성은 오래되고 사용하지 않는 객체를 캐싱하고 재초기화하여 객체 재사용을 더 잘 할 수 있게 해준다. 상태가 매우 가변적일 때는 일반적으로 공유가 불가능하다.

기타 주요 고려 사항으로는 검색(최종 클라이언트가 플라이웨이트에 접근하는 방법), 캐싱, 동시성 (컴퓨터 과학)이 있다. 팩토리 패턴 인터페이스는 플라이웨이트 객체를 생성하거나 재사용하기 위한 것으로, 복잡한 기본 시스템에 대한 퍼사드 패턴인 경우가 많다. 예를 들어, 팩토리 인터페이스는 플라이웨이트를 생성하기 위한 전역 액세스를 제공하기 위해 싱글톤 패턴으로 구현되는 경우가 많다.

일반적으로 검색 알고리즘은 팩토리 인터페이스를 통해 새로운 객체에 대한 요청으로 시작된다.

요청은 일반적으로 어떤 종류의 객체인지에 따라 적절한 캐시로 전달된다. 요청이 캐시의 객체로 충족되면, 이를 다시 초기화하여 반환할 수 있다. 그렇지 않으면 새로운 객체가 인스턴스화된다. 객체가 여러 개의 외부 하위 구성 요소로 분할된 경우, 객체가 반환되기 전에 해당 구성 요소들이 함께 조합될 것이다.

플라이웨이트 객체를 캐싱하는 방법에는 유지형 캐시와 비유지형 캐시 두 가지가 있다.

상태가 매우 가변적인 객체는 FIFO 구조로 캐싱할 수 있다. 이 구조는 사용하지 않는 객체를 캐시에 유지하며, 캐시를 검색할 필요가 없다.

반면, 비유지형 캐시는 초기 오버헤드가 적다. 캐시에 대한 객체는 컴파일 시 또는 시작 시 대량으로 초기화된다. 객체가 캐시에 채워지면 객체 검색 알고리즘은 유지형 캐시의 푸시/팝 연산보다 더 많은 오버헤드가 발생할 수 있다.

불변 상태를 가진 외부 객체를 검색할 때는 원하는 상태의 객체를 캐시에서 검색하기만 하면 된다. 그러한 객체가 없으면 해당 상태의 객체를 초기화해야 한다. 가변 상태를 가진 외부 객체를 검색할 때는 사용된 객체가 없으면 재초기화할 사용하지 않은 객체를 캐시에서 검색해야 한다. 사용하지 않은 객체가 없으면 새 객체를 인스턴스화하여 캐시에 추가해야 한다.

각 고유한 외부 객체 하위 클래스에 대해 별도의 캐시를 사용할 수 있다. 여러 캐시를 개별적으로 최적화하여 각 캐시에 고유한 검색 알고리즘을 연결할 수 있다. 이 객체 캐싱 시스템은 구성 요소 간의 느슨한 결합을 촉진하는 책임 연쇄 패턴으로 캡슐화할 수 있다.

플라이웨이트 객체가 여러 스레드에서 생성될 때는 특별한 고려가 필요하다. 값의 목록이 유한하고 미리 알려져 있다면, 플라이웨이트는 미리 인스턴스화되어 컨테이너에서 여러 스레드로 경합 없이 검색할 수 있다. 플라이웨이트가 여러 스레드에서 인스턴스화되는 경우 두 가지 옵션이 있다.

1. 플라이웨이트 인스턴스화를 단일 스레드로 만들어 경합을 도입하고 값당 하나의 인스턴스를 보장한다.

2. 동시 스레드가 여러 플라이웨이트 인스턴스를 생성하도록 허용하여 경합을 제거하고 값당 여러 인스턴스를 허용한다.

클라이언트와 스레드 간의 안전한 공유를 가능하게 하기 위해 플라이웨이트 객체는 불변 객체 값 객체로 만들 수 있으며, 두 인스턴스는 값이 같으면 동일한 것으로 간주된다.

C# 9의 이 예제는 레코드[7]를 사용하여 커피 맛을 나타내는 값 객체를 만든다.

```csharp

public record CoffeeFlavours(string flavour);

```

플라이웨이트 패턴으로 설계된 API에서 사용자는 `Flyweight` 클래스에 해당하는 인스턴스를 얻을 때, 직접 해당 클래스의 생성자를 호출하는 대신 `FlyweightFactory.getFlyweight()`에 접근한다.

한편, 호출된 `FlyweightFactory` 객체는 상황에 따라 다음과 같이 동작한다.


  • 해당 시점에서 대상 인스턴스가 생성되지 않은 경우:

1. 대상 인스턴스를 새로 생성한다.

2. 생성한 인스턴스를 풀링한다(다시 말해, 멤버의 컨테이너 객체에 저장한다).

3. 생성된 인스턴스를 반환한다.

  • 대상 인스턴스가 이미 생성된 경우:

1. 대상 인스턴스를 풀에서 호출한다.

2. 대상 인스턴스를 반환한다.

이처럼 `FlyweightFactory`에서는 상황에 따라 다른 처리가 이루어지지만, 사용자 측에서 얻는 결과는 완전히 동일하므로, 사용자는 `FlyweightFactory`의 내부 구조를 의식하지 않고 사용할 수 있다는 점이 특징이다.

플라이웨이트 패턴을 채택해야 할 전형적인 예는 불변 클래스를 다루는 경우이다. 불변 클래스란 인스턴스가 생성된 후에 해당 인스턴스의 상태가 변하지 않는 클래스이며, 자바에서는 `java.math.BigInteger`나 `java.awt.Color` 등이 있다. 자세한 내용은 불변 객체를 참조하라.

Java를 사용한 플라이웨이트 패턴의 예시는 다음과 같다. 이 소스 코드는 Java 1.5 이상의 버전에서 동작한다.

```java

import java.util.Map;

import java.util.HashMap;

import java.util.List;

import java.util.ArrayList;

class Stamp {

private final char type;

public Stamp(char type) {

this.type = type;

}

public void print() {

System.out.print(this.type);

}

}

class StampFactory {

private final Map pool;

public StampFactory() {

this.pool = new HashMap();

}

public Stamp get(char type) {

Stamp stamp = this.pool.get(type);

if (stamp == null) {

stamp = new Stamp(type);

this.pool.put(type, stamp);

}

return stamp;

}

public int getPoolSize() {

return this.pool.size();

}

}

public class FlyweightTest {

public static void main(String[] args) {

StampFactory factory = new StampFactory();

List stamps = new ArrayList();

stamps.add(factory.get('た'));

stamps.add(factory.get('か'));

stamps.add(factory.get('い'));

stamps.add(factory.get('た'));

stamps.add(factory.get('け'));

stamps.add(factory.get('た'));

stamps.add(factory.get('て'));

stamps.add(factory.get('か'));

stamps.add(factory.get('け'));

stamps.add(factory.get('た'));

for (Stamp s : stamps) {

s.print();

}

System.out.println();

System.out.println("Factory pool size = " + factory.getPoolSize());

}

}

```

소스 코드와 클래스 다이어그램의 대응 관계는 다음과 같다.

  • 플라이웨이트: `Stamp`
  • 플라이웨이트 팩토리: `StampFactory`
  • FlyweightFactory.getFlyweight(): `StampFactory.get()`


이 프로그램을 실행하면 "たかいたけたてかけた" (높은 대나무 세워 걸었다)라는 문자열을 출력한다. `FlyweightTest.main()` 내에서 `StampFactory.get()`를 10번 참조했지만, 그중 실제로 생성된 인스턴스는 5개("た", "か", "い", "け", "て")뿐이며, 인스턴스가 공유되고 있음을 알 수 있다.

5. 예제

플라이웨이트 패턴은 여러 프로그래밍 언어에서 구현될 수 있다.

C#에서는 `GraphicChar` 클래스를 통해 플라이웨이트 패턴을 구현한 예시를 볼 수 있다. `GraphicCharFactory`는 `GraphicChar` 객체를 생성하고 관리하며, 동일한 문자와 글꼴에 대해서는 객체를 공유한다.

PHP에서는 `커피향` 클래스를 통해 플라이웨이트 패턴을 구현한다. `커피향::인터닝()` 메서드는 동일한 향에 대한 객체 생성을 제한하고, `커피숍` 클래스에서 이를 활용하여 주문을 처리한다.

5. 1. Java

java

public enum FontEffect {

BOLD, ITALIC, SUPERSCRIPT, SUBSCRIPT, STRIKETHROUGH

}

public final class FontData {

/**

  • 약한 해시 맵은 사용하지 않는 FontData에 대한 참조를 삭제한다.
  • 값은 WeakReference로 래핑해야 한다.
  • 약한 해시 맵의 값 객체는 강한 참조로 유지되기 때문이다.
  • /

private static final WeakHashMap> flyweightData =

new WeakHashMap>();

private final int pointSize;

private final String fontFace;

private final Color color;

private final Set effects;

private FontData(int pointSize, String fontFace, Color color, EnumSet effects) {

this.pointSize = pointSize;

this.fontFace = fontFace;

this.color = color;

this.effects = Collections.unmodifiableSet(effects);

}

public static FontData create(int pointSize, String fontFace, Color color,

FontEffect... effects) {

EnumSet effectsSet = EnumSet.noneOf(FontEffect.class);

for (FontEffect fontEffect : effects) {

effectsSet.add(fontEffect);

}

// 객체를 생성하는 데 드는 비용이나 객체가 차지하는 메모리 공간에 대해 걱정할 필요가 없다.

FontData data = new FontData(pointSize, fontFace, color, effectsSet);

if (!flyweightData.containsKey(data)) {

flyweightData.put(data, new WeakReference(data));

}

// 해시값에 따라 변경 불가능한 단일 객체를 반환한다.

return flyweightData.get(data).get();

}

@Override

public boolean equals(Object obj) {

if (obj instanceof FontData) {

if (obj == this) {

return true;

}

FontData other = (FontData) obj;

return other.pointSize == pointSize && other.fontFace.equals(fontFace)

&& other.color.equals(color) && other.effects.equals(effects);

}

return false;

}

@Override

public int hashCode() {

return (pointSize * 37 + effects.hashCode() * 13) * fontFace.hashCode();

}

// FontData에 대한 Getter는 있지만 Setter는 없다. FontData는 불변 객체이다.

}

```

Java를 사용한 플라이웨이트 패턴의 예시이다. 이 코드는 Java 1.5 이상에서 동작한다.

```java

import java.util.Map;

import java.util.HashMap;

import java.util.List;

import java.util.ArrayList;

class Stamp {

private final char type;

public Stamp(char type) {

this.type = type;

}

public void print() {

System.out.print(this.type);

}

}

class StampFactory {

private final Map pool;

public StampFactory() {

this.pool = new HashMap();

}

public Stamp get(char type) {

Stamp stamp = this.pool.get(type);

if (stamp == null) {

stamp = new Stamp(type);

this.pool.put(type, stamp);

}

return stamp;

}

public int getPoolSize() {

return this.pool.size();

}

}

public class FlyweightTest {

public static void main(String[] args) {

StampFactory factory = new StampFactory();

List stamps = new ArrayList();

stamps.add(factory.get('た'));

stamps.add(factory.get('か'));

stamps.add(factory.get('い'));

stamps.add(factory.get('た'));

stamps.add(factory.get('け'));

stamps.add(factory.get('た'));

stamps.add(factory.get('て'));

stamps.add(factory.get('か'));

stamps.add(factory.get('け'));

stamps.add(factory.get('た'));

for (Stamp s : stamps) {

s.print();

}

System.out.println();

System.out.println("Factory pool size = " + factory.getPoolSize());

}

}

```

소스 코드와 클래스 다이어그램의 대응 관계는 다음과 같다.

  • 플라이웨이트: `Stamp`
  • 플라이웨이트 팩토리: `StampFactory`
  • FlyweightFactory.getFlyweight(): `StampFactory.get()`


이 프로그램을 실행하면 "たかいたけたてかけた" (높은 대나무 세워 걸었다)라는 문자열이 출력된다. `FlyweightTest.main()` 내에서 `StampFactory.get()`가 10번 호출되었지만, 실제로 생성된 인스턴스는 "た", "か", "い", "け", "て" 5개뿐이며, 인스턴스가 공유되고 있음을 알 수 있다.[1]

5. 2. C#

csharp

// 자체적으로 반복되는 플라이웨이트 객체를 정의합니다.

public class Flyweight

{

public string Name { get; set; }

public string Location { get; set; }

public string Website { get; set; }

public byte[] Logo { get; set; }

}

public static class Pointer

{

public static readonly Flyweight Company = new Flyweight { Name = "Abc", Location = "XYZ", Website = "www.example.com" };

}

public class MyObject

{

public string Name { get; set; }

public string Company => Pointer.Company.Name;

}

```

이 예제에서 `MyObject` 클래스의 모든 인스턴스는 데이터를 제공하기 위해 `Pointer` 클래스를 사용합니다.

5. 3. C++

C++ 표준 템플릿 라이브러리(STL)는 고유 객체를 키에 매핑할 수 있는 여러 컨테이너를 제공한다. 컨테이너를 사용하면 임시 객체를 생성할 필요가 없어 메모리 사용량을 더욱 줄일 수 있다.

```cpp

#include

#include

#include

// Tenant의 인스턴스는 플라이웨이트가 됩니다.

class Tenant {

public:

Tenant(const std::string& name = "") : m_name(name) {}

std::string name() const {

return m_name;

}

private:

std::string m_name;

};

// Registry는 Tenant 플라이웨이트 객체의 팩토리이자 캐시 역할을 합니다.

class Registry {

public:

Registry() : tenants() {}

Tenant& findByName(const std::string& name) {

if (!tenants.contains(name)) {

tenants[name] = Tenant{name};

}

return tenants[name];

}

private:

std::map tenants;

};

// Apartment는 고유한 tenant를 해당 방 번호에 매핑합니다.

class Apartment {

public:

Apartment() : m_occupants(), m_registry() {}

void addOccupant(const std::string& name, int room) {

m_occupants[room] = &m_registry.findByName(name);

}

void tenants() {

for (const auto &i : m_occupants) {

const int& room = i.first;

const auto& tenant = i.second;

std::cout << tenant->name() << " occupies room " << room << std::endl;

}

}

private:

std::map m_occupants;

Registry m_registry;

};

int main() {

Apartment apartment;

apartment.addOccupant("David", 1);

apartment.addOccupant("Sarah", 3);

apartment.addOccupant("George", 2);

apartment.addOccupant("Lisa", 12);

apartment.addOccupant("Michael", 10);

apartment.tenants();

return 0;

}

5. 4. PHP

php

<?php

class 커피향 {

private static array $CACHE = [];

private function __construct(private string $이름) {}

public static function 인터닝(string $이름): self {

self::$CACHE[$이름] ??= new self($이름);

return self::$CACHE[$이름];

}

public static function 캐시내향갯수(): int {

return count(self::$CACHE);

}

public function __toString(): string {

return $this->이름;

}

}

class 주문 {

private function __construct(

private 커피향 $향,

private int $테이블번호

) {}

public static function 생성(string $향이름, int $테이블번호): self {

$향 = 커피향::인터닝($향이름);

return new self($향, $테이블번호);

}

public function __toString(): string {

return "테이블 {$this->테이블번호}에 {$this->향} 제공";

}

}

class 커피숍 {

private array $주문 = [];

public function 주문받기(string $향, int $테이블번호) {

$this->주문[] = 주문::생성($향, $테이블번호);

}

public function 서비스() {

print(implode(PHP_EOL, $this->주문).PHP_EOL);

}

}

$가게 = new 커피숍();

$가게->주문받기("카푸치노", 2);

$가게->주문받기("프라페", 1);

$가게->주문받기("에스프레소", 1);

$가게->주문받기("프라페", 897);

$가게->주문받기("카푸치노", 97);

$가게->주문받기("프라페", 3);

$가게->주문받기("에스프레소", 3);

$가게->주문받기("카푸치노", 3);

$가게->주문받기("에스프레소", 96);

$가게->주문받기("프라페", 552);

$가게->주문받기("카푸치노", 121);

$가게->주문받기("에스프레소", 121);

$가게->서비스();

print("캐시 내 커피향 객체 수: ".커피향::캐시내향갯수().PHP_EOL);

6. 장점 및 단점

플라이웨이트 패턴으로 설계된 API에서 사용자는 `Flyweight` 클래스에 해당하는 인스턴스를 얻을 때, 직접 해당 클래스의 생성자를 호출하는 대신 `FlyweightFactory.getFlyweight()`에 접근한다.

호출된 `FlyweightFactory` 객체는 상황에 따라 다음과 같이 동작한다.


  • 해당 시점에서 대상 인스턴스가 생성되지 않은 경우:

1. 대상 인스턴스를 새로 생성한다.

2. 생성한 인스턴스를 풀링한다(다시 말해, 멤버의 컨테이너 객체에 저장한다).

3. 생성된 인스턴스를 반환한다.

  • 대상 인스턴스가 이미 생성된 경우:

1. 대상 인스턴스를 풀에서 호출한다.

2. 대상 인스턴스를 반환한다.

이처럼 `FlyweightFactory`에서는 상황에 따라 다른 처리가 이루어지지만, 사용자 측에서 얻는 결과는 완전히 동일하므로, 사용자는 `FlyweightFactory`의 내부 구조를 의식하지 않고 사용할 수 있다는 점이 특징이다.

플라이웨이트 패턴을 채택해야 할 전형적인 예는 불변 클래스를 다루는 경우이다. 불변 클래스란 인스턴스가 생성된 후에 해당 인스턴스의 상태가 변하지 않는 클래스이며, 자바에서는 `java.math.BigInteger`나 `java.awt.Color` 등이 있다. 자세한 내용은 불변 객체를 참조하라.

7. 활용 사례

플라이웨이트 패턴은 단순하고 반복적인 요소를 공유하여 대량의 객체를 처리할 때 메모리 사용량을 줄이는 데 유용하다. 공유되는 데이터는 외부 자료 구조에 보관하고, 객체가 사용될 때 임시로 전달하는 것이 일반적이다.[5]

예를 들어, 워드 프로세서에서 문자를 나타내는 데이터 구조를 생각해 보자. 각 문자에 글꼴, 글꼴 메트릭, 서식 데이터를 포함하는 글리프 객체를 할당하면 많은 메모리가 소모된다. 대신, 각 문자는 동일한 문자의 모든 인스턴스에서 공유되는 글리프 객체에 대한 참조를 가질 수 있다. 이렇게 하면 각 문자의 위치만 내부적으로 저장하면 된다.[5]

결과적으로 플라이웨이트 객체는 다음과 같은 특징을 가진다.[5]


  • 불변적이고, 컨텍스트에 독립적이며, 공유 가능한 ''고유'' 상태(예: 문자 'A'의 코드)를 저장한다.
  • 변동적이고, 컨텍스트에 종속적이며, 공유할 수 없는 ''외부'' 상태(예: 텍스트 문서에서 문자 'A'의 위치)를 전달하기 위한 인터페이스를 제공한다.


클라이언트는 `Flyweight` 객체를 재사용하고 필요에 따라 외부 상태를 전달하여 객체 생성 수를 줄일 수 있다. 플라이웨이트 패턴으로 설계된 API에서는 `FlyweightFactory.getFlyweight()`를 통해 `Flyweight` 클래스 인스턴스를 얻는다.[5]

`FlyweightFactory` 객체의 동작 방식은 다음과 같다.[5]

상황동작
대상 인스턴스가 생성되지 않은 경우
대상 인스턴스가 이미 생성된 경우



`FlyweightFactory`는 상황에 따라 다른 처리를 하지만, 사용자는 내부 구조를 의식하지 않고 동일한 결과를 얻을 수 있다.[5]

플라이웨이트 패턴은 불변 객체를 다루는 경우에 유용하다. 불변 클래스는 인스턴스 생성 후 상태가 변하지 않는 클래스이다. 자바에서는 `BigInteger`나 `Color` 등이 있다.[5]

8. 다른 패턴과의 관계

싱글톤 패턴에서 FlyweightFactory는 싱글톤으로 구현되는 경우가 많다.

9. 주의 사항

플라이웨이트 패턴은 많은 양의 메모리를 사용할 수 있는 작고 반복적인 요소를 공유하여 대량의 객체를 효율적으로 처리하는 데 유용하다. 공유되는 데이터는 외부 자료 구조에 보관하고, 객체가 사용될 때 임시로 전달하는 것이 일반적이다.[5]

워드 프로세서에서 문자를 표현하는 데이터 구조를 예로 들 수 있다. 각 문자에 글꼴, 글꼴 메트릭, 서식 데이터를 포함하는 글리프 객체를 할당하면 많은 메모리가 소모된다. 대신, 각 문자는 동일한 문자의 모든 인스턴스에서 공유되는 글리프 객체에 대한 참조를 가지도록 하고, 각 문자의 위치만 내부적으로 저장하면 메모리를 절약할 수 있다.[5]

플라이웨이트 객체는 다음과 같은 특징을 가진다.[5]


  • 불변적이고, 컨텍스트에 독립적이며, 공유 가능한 ''고유'' 상태 (예: 문자 집합에서 문자 'A'의 코드)를 저장한다.
  • 변동적이고, 컨텍스트에 종속적이며, 공유할 수 없는 ''외부'' 상태 (예: 텍스트 문서에서 문자 'A'의 위치)를 전달하기 위한 인터페이스를 제공한다.


클라이언트는 플라이웨이트 객체를 재사용하고 필요에 따라 외부 상태를 전달하여 객체 생성 수를 줄일 수 있다.

플라이웨이트 패턴을 구현할 때 고려해야 할 주요 사항은 다음과 같다.

  • 가변성: 외부 상태를 저장하는 객체가 변경될 수 있는지 여부이다. 불변 객체는 공유하기 쉽지만, 상태가 변경될 때마다 새로운 객체를 생성해야 한다. 가변 객체는 상태를 공유할 수 있지만, 객체 재사용이 더 복잡해질 수 있다.
  • 검색: 최종 클라이언트가 플라이웨이트에 접근하는 방법이다.
  • 캐싱: 객체를 캐싱하여 재사용성을 높일 수 있다.
  • 동시성 (컴퓨터 과학): 여러 스레드에서 플라이웨이트 객체가 생성될 때 동시성 문제를 고려해야 한다.


플라이웨이트 객체를 여러 스레드에서 생성하는 경우, 다음 두 가지 옵션 중 하나를 선택할 수 있다.

# 플라이웨이트 인스턴스화를 단일 스레드로 만들어 경합을 도입하고 값당 하나의 인스턴스를 보장한다.

# 동시 스레드가 여러 플라이웨이트 인스턴스를 생성하도록 허용하여 경합을 제거하고 값당 여러 인스턴스를 허용한다.

플라이웨이트 객체는 불변 객체 값 객체로 만들어 안전하게 공유할 수 있다. C# 9에서는 레코드[7]를 사용하여 값 객체를 만들 수 있다.

플라이웨이트 패턴으로 설계된 API에서 사용자는 `FlyweightFactory.getFlyweight()`를 통해 `Flyweight` 클래스의 인스턴스를 얻는다. `FlyweightFactory` 객체는 다음과 같이 동작한다.

  • 대상 인스턴스가 생성되지 않은 경우:

# 대상 인스턴스를 새로 생성한다.

# 생성한 인스턴스를 풀링한다.

# 생성된 인스턴스를 반환한다.

  • 대상 인스턴스가 이미 생성된 경우:

# 대상 인스턴스를 풀에서 호출한다.

# 대상 인스턴스를 반환한다.

`FlyweightFactory`는 상황에 따라 다른 처리를 하지만, 사용자는 내부 구조를 의식하지 않고 사용할 수 있다.

플라이웨이트 패턴은 불변 객체를 다루는 경우에 유용하다. 자바에서는 `BigInteger`나 `Color` 등이 불변 클래스의 예시이다.

`FlyweightFactory`에 저장된 인스턴스는 필요 없어진 경우에도 가비지 컬렉션되지 않으므로, 명시적으로 삭제해야 할 수 있다.

참조

[1] 서적 Design Patterns: Elements of Reusable Object-Oriented Software https://archive.org/[...] Addison Wesley
[2] 서적 Design Patterns: Elements of Reusable Object-Oriented Software Addison-Wesley
[3] 간행물 Proceedings of the 3rd annual ACM SIGGRAPH symposium on User interface software and technology - UIST '90 1990-10
[4] 간행물 ET++—an object oriented application framework in C++
[5] 웹사이트 Implementing Flyweight Patterns in Java https://www.develope[...] 2019-01-28
[6] 웹사이트 The Flyweight design pattern - Structure and Collaboration http://w3sdesign.com[...] 2017-08-12
[7] 웹사이트 Records - C# reference https://docs.microso[...] 2021-06-12



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com