2021. 12. 11. 19:56ㆍComputer Sciences/Design Patterns
개요
Flyweight는 격투기에서 가장 가벼운 몸무게 범주에 속한다. 그렇다면 이 패턴 또한 무언가를 가볍게 처리한다고 미루어 짐작할 수 있다.
Flyweight 패턴은 어떤 클래스의 인스턴스 한 개를 이용하여 가상 인스턴스를 제공하고 싶을 때 사용한다. 예를 들어 스타크래프트를 생각해보자. 마린(해병)은 움직이기, 총 쏘기, 스팀팩 등 모두 공통적인 기능을 사용한다. 이때 200명의 마린 인스턴스를 전부 생성한다고 어떨까? 분명 공통적인 부분은 전혀 변하지 않는다. 그러나 모든 인스턴스가 이를 각자 가지고 메모리에 올라가기 때문에 불필요한 메모리 낭비가 발생한다.
이 패턴을 다시 말하면 어떤 클래스의 공통적인 기능들을 하나의 인스턴스를 공유하도록 하여 메모리를 절약하는 패턴이다. 이제 예제 코드를 살펴보면서 자세히 알아보자.
예제 - 카운터 스트라이크
카운터 스트라이크는 해보신 분들도 있겠지만 테러리스트 팀과 대테러리스트 팀을 이루고 각 팀별로 미션을 클리어하면 승리하는 게임이다. 테러리스트 팀은 폭탄을 설치하고 터뜨리면 승리하고, 대테러리스트팀은 설치된 폭탄을 해체하면 승리한다. 그렇다면 여기서 생각해볼 것은 중복되는 부분과 그렇지 않은 부분이다. 예를 들어 각 팀의 멤버들은 서로 맡은 미션이 모두 같다. 그리고 무기를 소지한다는 점이 같다. 하지만 각자 서로 다른 무기를 사용할 수 있다. 따라서 테러리스트 플레이어 객체와 대테러리스트 플레이어 객체를 하나씩 만들어두고 각 인스턴스에 무기만 설정해주면 모든 멤버에 대한 인스턴스를 생성하지 않아도 게임을 할 수 있다.
여기서 객체를 하나씩 만들어 둔다는 점을 생각해보자. 여기에 대해서는 두 가지 방법이 있다. 싱글턴 패턴과 팩토리 패턴이다. 여기서는 팩토리 패턴을 사용하도록 하겠다.
Class Diagram
코드
// 두 팀의 멤버 모두에게 부여된 공통 부분 추상화
public interface Player {
void assignWeapon(String weapon);
void mission();
}
public class Terrorist implements Player {
private final String TASK;
private String weapon;
public Terrorist() {
TASK = "PLANT A BOMB";
}
// 무기는 설정할 수 있도록 setter를 열어 둔다.
@Override
public void assignWeapon(String weapon) {
this.weapon = weapon;
}
@Override
public void mission() {
System.out.println("Terrorist with weapon " +
weapon + " |" + " Task is " + TASK);
}
}
public class CounterTerrorist implements Player {
private final String TASK;
private String weapon;
public CounterTerrorist() {
TASK = "DIFFUSE BOMB";
}
// 무기는 설정할 수 있도록 setter를 열어 둔다.
@Override
public void assignWeapon(String weapon) {
this.weapon = weapon;
}
@Override
public void mission() {
System.out.println("Counter Terrorist with weapon " +
weapon + " |" + " Task is " + TASK);
}
}
public class PlayerFactory {
// 멀티 쓰레드 환경이라면 ConcurrentHashMap을 사용하자.
private static HashMap<String, Player> hm = new HashMap<>();
// 이미 인스턴스가 존재하면 해당 인스턴스를 반환하고 없으면 생성한다.
public static Player getPlayer(String type) {
Player p = null;
if (hm.containsKey(type)) {
p = hm.get(type);
} else {
switch (type) {
case "Terrorist":
System.out.println("Terrorist created");
p = new Terrorist();
break;
case "CounterTerrorist":
System.out.println("CounterTerrorist created");
p = new CounterTerrorist();
break;
default:
System.out.println("Unreachable code!");
}
hm.put(type, p);
}
return p;
}
}
public class CounterStrike {
private static String[] playerType = { "Terrorist", "CounterTerrorist" };
private static String[] weapons =
{ "AK-47", "Maverick", "Gut Knife", "Desert Eagle" };
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Player player = PlayerFactory.getPlayer(getRandPlayerType());
player.assignWeapon(getRandWeapon());
player.mission();
}
}
public static String getRandPlayerType() {
Random r = new Random();
int randInt = r.nextInt(playerType.length);
return playerType[randInt];
}
public static String getRandWeapon() {
Random r = new Random();
int randInt = r.nextInt(weapons.length);
return weapons[randInt];
}
}
장단점
장점
- 실행 시 인스턴스의 개수를 줄여 메모리를 절약할 수 있다.
- 여러 가상 객체의 상태를 한 곳에 집중시켜놓을 수 있다.
- 어떤 클래스의 인스턴스가 아주 많이 필요하지만 모두 똑같은 방식으로 제어할 수 있는 경우에 유용하다.
단점
- 특정 인스턴스만 다른 행동을 하는 것이 불가능하게 된다. 즉 모든 인스턴스가 동일하게 행동한다.
'Computer Sciences > Design Patterns' 카테고리의 다른 글
Interpreter Pattern (0) | 2021.12.12 |
---|---|
Memento Pattern (0) | 2021.12.11 |
Visitor Pattern (0) | 2021.12.11 |
Prototype Pattern (0) | 2021.12.11 |
14. Chain of Responsibility Pattern (0) | 2021.11.14 |