플라이 웨이트 (flyweight pattern) -wr
오브젝트의 공통된 데이터를 공유함으로 메모리사용을 줄여주는 패턴이다. 즉 인스턴스 생성시 메모리 낭비를 줄이려는 목적이다.
- 어떤 객체를 사용하기 위해 매번 인스턴스를 생성하지 않는다. 한번만 사용하고 다시 필요할때 이전에 생성된 객체를 재사용 할 수있다.
- 객체생성시 많은 자원을 소모할 경우 해당 패턴을 이용하여 적은 자원으로 객체를 재사용 할 수있다.
예시)
게임의 예시가 가장 올바르게 보인다. 마인크레프트나 심시티같은 게임상에서 나무를 표현할때 혹은 스타그래프트와 같은 전략 게임에서 유닛에 대해 생성을 할때 같은 오브젝트가 있다고 하면 객체를 하나의 팩토리에 저장해 두었다가 재사용 할 수 있다. 만약 이것을 재사용하지 않고 생성될때마다 새롭게 인스턴스를 생성한다면 메모리는 새로운 객체들의 생성을 감당하지 못하고 셧다운될 수 있다.
다음과 같이 스타크레프트 게임내에서 서로 같은 테란종족을 이용시 은색과, 자주색으로 표현되는 골리앗들을 생성 할 수 있다. 이때마다 골리앗 유닛의 인스턴스를 새롭게(NEW) 생성하게 되면 메모리는 견디지 못하고 폭발하고 말것이다. 이때 flyweight pattern 을 사용하여 객체를 재사용하는 방법을 모색해보자.
UML Model
Unit Model
@ToString
@Getter@Setter
abstract public class Unit {
public Unit(String color) {
this.color = color;
}
private String color;
public abstract void position(double x, double y ) ;
}
@ToString(callSuper = true)
@Getter@Setter
public class Goliath extends Unit {
public Goliath(String color) {
super(color);
}
// 테란 차량 장갑 (upgrade level)
//private String terranVehiclePlating;
// 2연장 자동포 (upgrade level)
//private String twinAutocannons;
// 지옥불 미사일 (upgrade level)
//private String hellfireMissilePack;
// 카론 증폭기 (upgrade level)
//private String charonBoosters;
@Override
public void position(double x, double y) {
System.out.println(this + " \t location -> x : " + x + "y : " + y);
}
}
public class Marin extends Unit {
public Marin(String color) {
super(color);
}
}
Context Factory
public class ItemFactory {
private final Map<String, Unit> unitPool = new HashMap<>();
public Unit create(String key, String type) {
if (unitPool.containsKey(key)) {
System.out.print("repair : ");
return unitPool.get(key);
} else {
var item = switch (type) {
case "goliath" -> new Goliath(key);
case "marin" -> new Marin(key);
default -> throw new IllegalArgumentException("type 이 존재 하지 않습니다.");
};
System.out.print("New : ");
unitPool.put(key,item);
return item;
}
}
}
concrete main class
public class FlyweightClientMain {
public static void main(String[] args) {
String[] color = new String[]{"red", "blue", "silver"};
Random random = new Random();
ItemFactory itemFactory = new ItemFactory();
IntStream.rangeClosed(0, 10).forEach(index -> {
Unit unit = itemFactory.create(color[random.nextInt(3)] + "_goliath", "goliath");
unit.position(Math.random(), Math.random());
});
IntStream.rangeClosed(0, 10).forEach(index -> {
Unit unit = itemFactory.create(color[random.nextInt(3)] + "_marin", "marin");
unit.position(Math.random(), Math.random());
});
}
}
결과
New : Goliath(super=Unit(color=red_goliath)) location -> x : 0.9904424511707278y : 0.5039170798454911
New : Goliath(super=Unit(color=silver_goliath)) location -> x : 0.7849199311814581y : 0.35068515401388767
repair : Goliath(super=Unit(color=red_goliath)) location -> x : 0.8200938816311484y : 0.4993358655743728
repair : Goliath(super=Unit(color=red_goliath)) location -> x : 0.35809939042228744y : 0.40194658297590846
New : Goliath(super=Unit(color=blue_goliath)) location -> x : 0.6375459919441778y : 0.4774845986976154
repair : Goliath(super=Unit(color=red_goliath)) location -> x : 0.1559955772508459y : 0.921233703653204
repair : Goliath(super=Unit(color=red_goliath)) location -> x : 0.4072820682847047y : 0.26154676145015887
repair : Goliath(super=Unit(color=silver_goliath)) location -> x : 0.5858193988400549y : 0.03363884516087312
repair : Goliath(super=Unit(color=red_goliath)) location -> x : 0.048306728623218054y : 0.5112765335460607
repair : Goliath(super=Unit(color=red_goliath)) location -> x : 0.41211143604352296y : 0.27843938392789036
repair : Goliath(super=Unit(color=silver_goliath)) location -> x : 0.023190833610511885y : 0.21499504258061874
New : Unit(color=blue_marin) location -> x : 0.11442953091933339y : 0.5344290206485512
New : Unit(color=red_marin) location -> x : 0.5330742722011527y : 0.05961658453073304
repair : Unit(color=blue_marin) location -> x : 0.19451566469170833y : 0.3070585425530875
repair : Unit(color=red_marin) location -> x : 0.9356006430932778y : 0.9140866106169913
New : Unit(color=silver_marin) location -> x : 0.726185417162103y : 0.8710693772490236
repair : Unit(color=silver_marin) location -> x : 0.0506092921183946y : 0.18602784088092228
repair : Unit(color=blue_marin) location -> x : 0.004659666158370435y : 0.4273415318643866
repair : Unit(color=red_marin) location -> x : 0.34751154004403373y : 0.8670546377623075
repair : Unit(color=silver_marin) location -> x : 0.496785509190364y : 0.12896705645457207
repair : Unit(color=silver_marin) location -> x : 0.6186298558019877y : 0.6691297680133091
repair : Unit(color=blue_marin) location -> x : 0.41956924017340513y : 0.5590991556748193
Process finished with exit code 0
결과를 보면 같은 컬러를 가진 유닛(골리앗)은 unitPool 에서 꺼내와 새롭게 인스턴스를생성(New) 하지않고 가져와 재사용하는것을 볼 수 있다.
또다른 예제로 GTA 같은 게임에서 자동차를 생성할때 Object를 생성해야하다보면 많은 양의 메모리가 필요하게 될텐데 이때도 해당 패턴을 사용하여 객체를 재사용할 수 있다.
UML Model
- CarFactory 에 carList poll을 만들어 두고 재사용 할 수 있도록 한다.
Car Model
public interface Car {
public void build();
}
public class BMW implements Car{
@Override
public void build() {
System.out.println("BMW car build");
}
}
public class Hyundai implements Car{
String cylinder;
@Override
public void build() {
System.out.println("Hyundai car build");
}
}
public class Benz implements Car{
@Override
public void build() {
System.out.println("BENZ car build");
}
}
Car Factory
public class CarFactory {
private final Map<String, Car> carPool = new HashMap<>();
public Car createCar(String carName, String cylinder) {
Car car = carPool.get(carName);
if (car == null) {
var newCar = switch (carName) {
case "benz" -> new Benz();
case "bmw" -> new BMW();
case "hyundai" -> new Hyundai();
default -> throw new IllegalArgumentException("type 이 존재 하지 않습니다.");
};
carPool.put(carName, newCar);
car = newCar;
System.out.println("새로운 인스턴스를 생성합니다 new :" + carName);
}
return car;
}
}
Main
public class FlyweightClientMain2 {
public static void main(String[] args) {
final CarFactory carFactory = new CarFactory();
IntStream.rangeClosed(0,5).forEach(value -> {
Car car = carFactory.createCar("benz","V6");
car.build();
});
IntStream.rangeClosed(0,5).forEach(value -> {
Car car = carFactory.createCar("hyundai","V6");
car.build();
});
IntStream.rangeClosed(0,5).forEach(value -> {
Car car = carFactory.createCar("bmw","V6");
car.build();
});
}
}
실행결과
새로운 인스턴스를 생성합니다 new :benz
BENZ car build
BENZ car build
BENZ car build
BENZ car build
BENZ car build
BENZ car build
새로운 인스턴스를 생성합니다 new :hyundai
Hyundai car build
Hyundai car build
Hyundai car build
Hyundai car build
Hyundai car build
Hyundai car build
새로운 인스턴스를 생성합니다 new :bmw
BMW car build
BMW car build
BMW car build
BMW car build
BMW car build
BMW car build
기존에 있던 Obejct를 재사용하고 없던 내역은 새로운 인스턴스를 생성해서 사용함으로 메모리를 효율적으로 사용 할 수 있게 해준다.
참고
https://gameprogrammingpatterns.com/flyweight.html
https://www.learn-it-with-examples.com/development/java/java-design-patterns/java-flyweight.html
'소프트웨어공학 > Design Pattern' 카테고리의 다른 글
어텝터(adapter) (0) | 2023.01.11 |
---|---|
프록시 패턴(proxy) (0) | 2023.01.05 |
책임연쇠(chain-of-responsibility pattern) (0) | 2023.01.05 |
추상 팩토리 (abstract factory Pattern ) (2) | 2022.12.15 |
팩토리 메소드 패턴(Factory method pattern / Factory pattern) (0) | 2022.12.09 |