의도
- 플라이웨이트 -> 각 객체에 모든 데이터를 유지하는 대신 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();
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');
addCarToPoliceDatabase(factory, 'CL234IR', 'James Doe', 'BMW', 'X1', 'red');
factory.listFlyweights();
적용
- 프로그램에 많은 수의 객체가 필요해 RAM이 부족할떄만 사용
앱에 비슷한 다수의 유사 객체를 사용할 때, 사용 장치의 RAM이 부족해 최적화가 필요할 때, 중복되는 상태들이 많아 추출해 공유할 필요가 있을 때 사용하는 것이 적절함
구현 방법
- 플레이웨이트가 될 클래스 필드를 변경 가능 여부에 따라 2개로 구분
- 클래스의 고유한 상태를 나타내는 필드들을 변경할 수 없도록 하고, 생성자 내부에서만 초기화 되도록 설정해야 함
- 공유되는 상태들을 사용하는 메서드를 확인해 새로운 매개변수롤 도입하고 기존의 필드를 대체
- 필요하다면 플라이웨이트들을 관리하는 팩토리 클래스들을 생성
- 클라이언트는 플라이웨이트 객체드의 메서드들을 호출할 수 있도록 공유한 상태의 값들을 저장하거나 계산할 수 있어야 함
플라이웨이트를 참조하는 필드와 공유하는 상태는 별도의 컨텍스트 클래스로 이동시킬수도 있음
장단점
- 프로그램에 유사한 객체들이 많을 때 RAM절약이 가능함
- 플라이웨이트 객체 호출시 컨텍스트 데이터를 다시 계산해야 하면 CPU 자원 낭비 발생 가능
- 의미상 같이 있어야 하는 코드들이 서로 분리되어 구조가 복잡해짐
출처:
https://refactoring.guru/ko/design-patterns/flyweight