Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FlyWeight 패턴(김종민) #169

Open
rlajm1203 opened this issue May 22, 2024 · 0 comments
Open

FlyWeight 패턴(김종민) #169

rlajm1203 opened this issue May 22, 2024 · 0 comments

Comments

@rlajm1203
Copy link
Collaborator

rlajm1203 commented May 22, 2024

문제가 무엇인가?

FlyWeight 패턴이란 무엇인가? (날짜, 시간 API와 관련 없음)

왜 이런 문제를 선정하였는가?

모자액 12장 날짜와 시간 API를 읽고 관련해서 더 공부할 내용을 찾아보다가 새로운 키워드를 발견하여 선정하였습니다.

자신이 생각한 답변은 무엇인가?

FlyWeight 패턴이란 무엇인가?

  1. FlyWeight Pattern은 재사용 가능한 객체 인스턴스를 공유시켜 메모리 사용량을 최소화 하는 패턴이다.
  2. 프로그램에서 많은 객체를 생성해야 하는 경우, 객체들의 일부 데이터를 공유하여 메모리 소비를 줄이는 방식으로 동작한다.
  3. 간단히 말하면, 캐시(Cache) 개념을 코드로 패턴화한 것으로 보면 되는데, 자주 변하는 속성(extrinsit)과 변하지 않는 속성 (intrinsit)을 분리하고 변하지 않는 속성을 캐시하여 재사용해 메모리 사용량을 줄이는 방식이다.

두 가지 개념

Intrinsit(내재 상태)

객체 간에 공유할 수 있는 불변 데이터. 날짜/시간 API에서는 시간대나 로케일 정보가 여기에 해당한다.

Extrinsit(외재 상태)

객체마다 다른 데이터로, 공유할 수 없다. 예를 들어, 특정 날짜나 시간 값이 여기에 해당한다.

FlyWeight 패턴에서는 내재 상태를 가진 객체를 FlyWeight 객체라고 부르며, 별도의 풀(Pool)에 저장하여 재사용한다.

클라이언트는 필요한 FlyWeight 객체를 FlyWeightFactory로부터 받아와 사용하고, 사용 후에는 다시 풀에 반환한다.

예시

나무 심기 예시

  1. 숲을 만들기 위해서 지형(Terrain)에 나무 객체들을 심으려고 한다.
  2. 이 나무(Tree) 객체에 대해 필요한 데이터는 다음과 같다.

나무 종류
mesh
나무껍질 텍스쳐
잎 텍스쳐
위치 매개변수 (x,y)

나무의 종류에도 여러가지가 있으며, 나무의 형태를 결정하는 mesh와 texture 그리고 나무가 어느 지형에 심어질지에 대한 x,y 위치 매개 변수가 필요하다.

적용 코드 (FlyWeight 객체 정의)

// ConcreteFlyWeight
final class TreeModel {
    // 메시, 텍스쳐 총 사이즈
    long objSize = 90; // 90MB

    String type; // 나무 종류
    Object mesh; // 메쉬
    Object texture; // 나무 껍질 + 잎사귀 텍스쳐

    public TreeModel(String type, Object mesh, Object texture) {
        this.type = type;
        this.mesh = mesh;
        this.texture = texture;

        // 나무 객체를 생성하여 메모리에 적재했으니 메모리 사용 크기 증가
        Memory.size += this.objSize;
    }
}
// UnsahredConcreteFlyweight
class Tree {
    // 죄표값과 나무 모델 참조 객체 크기를 합친 사이즈
    long objSize = 10; // 10MB

    // 위치 변수
    double position_x;
    double position_y;

    // 나무 모델
    TreeModel model;

    public Tree(TreeModel model, double position_x, double position_y) {
        this.model = model;
        this.position_x = position_x;
        this.position_y = position_y;

        // 나무 객체를 생성하였으니 메모리 사용 크기 증가
        Memory.size +=  this.objSize;
    }
}

이때 Tree 클래스와 TreeModel 간의 관계를 맺어주어야 하는데, 상속(Heritance)을 통해서 해줘도 되고 합성(Composition)을 통해서 맺어도 된다.

그리고 ConcreteFlyWeight인 TreeModel 클래스를 final화시켜 불변 객체로 만들어준다.
TreeModel의 texture과 mesh는 중간에 변경될 일이 없기 때문이다.

FlyWeight 팩토리 정의

// FlyweightFactory
class TreeModelFactory {
    // Flyweight Pool - TreeModel 객체들을 Map으로 등록하여 캐싱
    private static final Map<String, TreeModel> cache = new HashMap<>(); // static final 이라 Thread-Safe 함

    // static factory method
    public static TreeModel getInstance(String key) {
        // 만약 캐시 되어 있다면
        if(cache.containsKey(key)) {
            return cache.get(key); // 그대로 가져와 반환
        } else {
            // 캐시 되어있지 않으면 나무 모델 객체를 새로 생성하고 반환
            TreeModel model = new TreeModel(
                    key,
                    new Object(),
                    new Object()
            );
            System.out.println("-- 나무 모델 객체 새로 생성 완료 --");

            // 캐시에 적재
            cache.put(key, model);

            return model;
        }
    }
}

FlyWeight Factory의 핵심은 다음과 같다.

  1. FlyWeightPool : HashMap 컬렉션을 통해 Key와 나무 모델 객체를 저장하는 캐시 저장소 역할
  2. getinstance 메소드 : Pool에 가져오고자 하는 객체가 있는지 검사를 하여 있으면 그대로 반환, 없으면 새로 생성
// FlyweightFactory
class TreeModelFactory {
    // Flyweight Pool - TreeModel 객체들을 Map으로 등록하여 캐싱
    private static final Map<String, TreeModel> cache = new HashMap<>(); // static final 이라 Thread-Safe 함

    // static factory method
    public static TreeModel getInstance(String key) {
        // 만약 캐시 되어 있다면
        if(cache.containsKey(key)) {
            return cache.get(key); // 그대로 가져와 반환
        } else {
            // 캐시 되어있지 않으면 나무 모델 객체를 새로 생성하고 반환
            TreeModel model = new TreeModel(
                    key,
                    new Object(),
                    new Object()
            );
            System.out.println("-- 나무 모델 객체 새로 생성 완료 --");

            // 캐시에 적재
            cache.put(key, model);

            return model;
        }
    }
}

Client 최적화 하기

기존 Terrain 클래스의 나무를 생성하는 render() 메소드의 내부 로직은 단순히 사용자로부터 매개변수를 받아 그대로 tree 객체를 생성할 뿐이다.

하지만 기존 tree 객체를 따로 TreeModel로 나누고, TreeModelFactory까지 생성했으니 이제 이들을 이용하는 클라이언트의 코드를 최적화 작업을 진행해 주어야 한다.

최적화 작업은 아래와 같다.

  1. TreeModel에서 공유되고 있는 나무 모델 객체를 가져온다. (없다면 생성)
  2. 가져온 나무 모델과 좌표값을 이용해 나무 객체를 새로 생성한다.
// Client
class Terrain {
    // 지형 타일 크기
    static final int CANVAS_SIZE = 10000;

    // 나무를 렌더릴
    public void render(String type, double position_x, double position_y) {
        // 1. 캐시 되어 있는 나무 모델 객체 가져오기
        TreeModel model = TreeModelFactory.getInstance(type);

        // 2. 재사용한 나무 모델 객체와 변화하는 속성인 좌표값으로 나무 생성
        Tree tree = new Tree(model, position_x, position_y);

        System.out.println("x:" + tree.position_x + " y:" + tree.position_y + " 위치에 " + type + " 나무 생성 완료");
    }
}
public static void main(String[] args) {
    // 지형 생성
    Terrain terrain = new Terrain();

    // 지형에 Oak 나무 5 그루 생성
    for (int i = 0; i < 5; i++) {
        terrain.render(
                "Oak", // type
                Math.random() * Terrain.CANVAS_SIZE, // position_x
                Math.random() * Terrain.CANVAS_SIZE // position_y
        );
    }

    // 지형에 Acacia 나무 5 그루 생성
    for (int i = 0; i < 5; i++) {
        terrain.render(
                "Acacia", // type
                Math.random() * Terrain.CANVAS_SIZE, // position_x
                Math.random() * Terrain.CANVAS_SIZE // position_y
        );
    }

    // 지형에 Jungle 나무 5 그루 생성
    for (int i = 0; i < 5; i++) {
        terrain.render(
                "Jungle", // type
                Math.random() * Terrain.CANVAS_SIZE, // position_x
                Math.random() * Terrain.CANVAS_SIZE // position_y
        );
    }

    // 총 메모리 사용률 출력
    Memory.print();
}
image

결과

  • FlyWeight 패턴 미적용 (모든 객체를 하나하나 생성할 경우) : 1500MB 사용
  • FlyWeight 패턴 적용 : 420MB사용
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant