Flyweight[Design Pattern]

SnowCat·2023년 3월 14일
0

Design Pattern

목록 보기
12/23
post-thumbnail

의도

  • 플라이웨이트 -> 각 객체에 모든 데이터를 유지하는 대신 RAM에 더 많은 객체들을 포함하게 하는 디자인 패턴

문제점

  • 게임 프로그램을 제작하고 있다 해보자. 이 때 어느 디버그 로그를 분석한 결과, 메모리 부족으로 인해 게임이 충돌할 가능성이 발견되었다.
  • 현재 Particle 객체는 좌표, 벡터 등과 같은 많은 데이터를 포함하는 객체로 되어있어서 새로운 입자를 생성할 때 RAM이 부족해 게임이 충돌하는 문제점을 안고 있다.

해결책

  • 객체 외부에서 변경이 가능한 상태와 객체 외부에서 변경이 불가능한 상태로 나누어 state를 저장
  • 객체 내부에 반복되지 않고 가변적인 state 저장을 중단하고, 대신 가변적인 상태를 이 상태에 의존하는 특정 메서드들에 전달함
  • 이제 고유한 상태값들은 여러 곳에서 재사용할 수 있음
  • 고유한 상태값은 변형이 가능한 상태값들에 비해 적은 수에 객체만 있으면 되기 때문에 메모리 절약이 가능해짐

구조

/*
 * 여러 객체들 사이에 공유되는 데이터를 저장하는 부분
 * 메모리 절약만을 위한 패턴이기 때문에 플라이웨이트 패턴 사용 전 다른 패턴이 적용한지 확인
 */
class Flyweight {
    private sharedState: any;

    constructor(sharedState: any) {
        this.sharedState = sharedState;
    }

    public operation(uniqueState): void {
        const s = JSON.stringify(this.sharedState);
        const u = JSON.stringify(uniqueState);
        console.log(`Flyweight: Displaying shared (${s}) and unique (${u}) state.`);
    }
}

/*
 * 플라이웨이트에 편하게 접근하기 위해서 팩도리 메서드를 생성
 * 팩토리에서는 클라이언트에서 원하는 플라이웨이트 상태를 찾음
 * 요구하는 상태가 있을 경우 기존 상태 객체를 반환하고, 아닐 경우 새로운 상태를 생성함
 */
class FlyweightFactory {
    private flyweights: {[key: string]: Flyweight} = <any>{};

    constructor(initialFlyweights: string[][]) {
        for (const state of initialFlyweights) {
            this.flyweights[this.getKey(state)] = new Flyweight(state);
        }
    }

    private getKey(state: string[]): string {
        return state.join('_');
    }

    public getFlyweight(sharedState: string[]): Flyweight {
        const key = this.getKey(sharedState);

        if (!(key in this.flyweights)) {
            console.log('FlyweightFactory: Can\'t find a flyweight, creating new one.');
            this.flyweights[key] = new Flyweight(sharedState);
        } else {
            console.log('FlyweightFactory: Reusing existing flyweight.');
        }

        return this.flyweights[key];
    }

    public listFlyweights(): void {
        const count = Object.keys(this.flyweights).length;
        console.log(`\nFlyweightFactory: I have ${count} flyweights:`);
        for (const key in this.flyweights) {
            console.log(key);
        }
    }
}

// 플라이웨이트 객체를 저장하는 클라이언트 코드
const factory = new FlyweightFactory([
    ['Chevrolet', 'Camaro2018', 'pink'],
    ['Mercedes Benz', 'C300', 'black'],
    ['Mercedes Benz', 'C500', 'red'],
    ['BMW', 'M5', 'red'],
    ['BMW', 'X6', 'white'],
    // ...
]);
factory.listFlyweights();
/*
FlyweightFactory: I have 5 flyweights:
Chevrolet_Camaro2018_pink
Mercedes Benz_C300_black
Mercedes Benz_C500_red
BMW_M5_red
BMW_X6_white
*/


// 클라이언트 코드에서 플라이웨이트의 공유된 상태를 저장해 새로운 객체를 저장하게 됨
function addCarToPoliceDatabase(
    ff: FlyweightFactory, plates: string, owner: string,
    brand: string, model: string, color: string,
) {
    console.log('\nClient: Adding a car to database.');
    const flyweight = ff.getFlyweight([brand, model, color]);

    flyweight.operation([plates, owner]);
}

addCarToPoliceDatabase(factory, 'CL234IR', 'James Doe', 'BMW', 'M5', 'red');
/*
Client: Adding a car to database.
FlyweightFactory: Reusing existing flyweight.
Flyweight: Displaying shared (["BMW","M5","red"]) and unique (["CL234IR","James Doe"]) state.
*/

addCarToPoliceDatabase(factory, 'CL234IR', 'James Doe', 'BMW', 'X1', 'red');
/*
Client: Adding a car to database.
FlyweightFactory: Can't find a flyweight, creating new one.
Flyweight: Displaying shared (["BMW","X1","red"]) and unique (["CL234IR","James Doe"]) state.
*/

factory.listFlyweights();
/*
FlyweightFactory: I have 6 flyweights:
Chevrolet_Camaro2018_pink
Mercedes Benz_C300_black
Mercedes Benz_C500_red
BMW_M5_red
BMW_X6_white
BMW_X1_red
*/

적용

  • 프로그램에 많은 수의 객체가 필요해 RAM이 부족할떄만 사용
    앱에 비슷한 다수의 유사 객체를 사용할 때, 사용 장치의 RAM이 부족해 최적화가 필요할 때, 중복되는 상태들이 많아 추출해 공유할 필요가 있을 때 사용하는 것이 적절함

구현 방법

  1. 플레이웨이트가 될 클래스 필드를 변경 가능 여부에 따라 2개로 구분
  2. 클래스의 고유한 상태를 나타내는 필드들을 변경할 수 없도록 하고, 생성자 내부에서만 초기화 되도록 설정해야 함
  3. 공유되는 상태들을 사용하는 메서드를 확인해 새로운 매개변수롤 도입하고 기존의 필드를 대체
  4. 필요하다면 플라이웨이트들을 관리하는 팩토리 클래스들을 생성
  5. 클라이언트는 플라이웨이트 객체드의 메서드들을 호출할 수 있도록 공유한 상태의 값들을 저장하거나 계산할 수 있어야 함
    플라이웨이트를 참조하는 필드와 공유하는 상태는 별도의 컨텍스트 클래스로 이동시킬수도 있음

장단점

  • 프로그램에 유사한 객체들이 많을 때 RAM절약이 가능함
  • 플라이웨이트 객체 호출시 컨텍스트 데이터를 다시 계산해야 하면 CPU 자원 낭비 발생 가능
  • 의미상 같이 있어야 하는 코드들이 서로 분리되어 구조가 복잡해짐

출처:
https://refactoring.guru/ko/design-patterns/flyweight

profile
냐아아아아아아아아앙

0개의 댓글