플라이웨이트 패턴 (Flyweight Pattern)
특정한 클래스의 인스턴스 하나로 여러 개의 가상 인스턴스를 제공한다.
코드에 적용해보기
'꿈의 마을'과 같은 도시 건설 게임을 만든다고 하자. 간단하게 나무를 심는 로직을 설계한다고 할 때 이 나무들을 각각 x, y 좌표를 가지고 있다.
Tree 인스턴스에는 나무의 상태가 저장이 되며, display() 메소드에 x, y 좌표, 나이 등을 바탕으로 화면에 나무를 그린다. 각각의 속성을 그리는데 필요한 데이터가 x: 4byte, y:4byte라고 했을 때 하나의 나무를 그리면 최소 8byte이상을 차지한다. 이를 수백, 수천개 그린다면 어떨까, 나무를 그리면 그릴 수록 데이터를 많이 차지한다. 메모리는 한정적인 자원이기 때문에 수억개의 나무를 그리면 더 이상 프로그램이 돌아가지 않는 지경에 다다른다. 플라이웨이트 패턴으로 이를 해결할 수 있다. 플라이웨이트는 경량이라는 의미로 객체 간에 데이터를 공유하여 프로그램이 사용하는 메모리 양을 최소화한다는 의미를 담고있다.
나무의 인스턴스를 매번 새로 만드는 게 아니라 이미 만들어진 것을 가져다 사용하는 방식이다.
public interface Tree {
public void display(int x, int y);
public default boolean isWithinRange(LocalDate aDate) {
Month month = aDate.getMonth();
return (month.getValue() > 2) && (month.getValue() < 11);
}
}
인스턴스를 하나만 만든다는 것을 명확히 설명하기 위해 침엽수와 낙엽수로 종류를 나누기 위해 인터페이스로 Tree를 구현했다.
public class ConiferTree implements Tree {
public void display(int x, int y) {
System.out.println("침엽수의 위치 " + x + ", " + y);
}
}
public class DeciduousTree implements Tree {
public void display(int x, int y) {
System.out.println("낙엽수의 위치 " + x + ", " + y);
if (!this.isWithinRange(LocalDate.now())) {
System.out.println("나무에 잎이 없습니다!");
}
}
}
특히 낙엽수는 나뭇잎이 떨어지는 특징이 있기 때문에 LocalDate 타입을 이용하여 현재 시간과 비교한다. 단순히 위치만을 표시하는 침엽수 클래스에 비해 나무를 그리기 위한 다양한 메소드를 가지고 있다.
public class TreeFactory {
Tree d, c = null;
public TreeFactory() {
this.d = new DeciduousTree();
this.c = new ConiferTree();
}
public Tree getTree(String type) throws Exception {
if (type.equals("낙엽수")) {
return this.d;
} else if (type.equals("침엽수")) {
return this.c;
} else {
throw new Exception("해당 나무는 존재하지 않습니다.");
}
}
}
브리지 패턴에서도 등장했듯이 상황에 맞는 인스턴스를 생성해주기 위해 팩토리 패턴을 이용한다.
public class Client {
public static void main(String[] args) {
int[][] deciduousLocations = {{1, 1}, {33, 50}, {100, 90}};
int[][] coniferLocations = {{10, 87}, {24, 76}, {2, 64}};
TreeFactory treeFactory = new TreeFactory();
Tree d, c;
try {
d = treeFactory.getTree("낙엽수");
c = treeFactory.getTree("침엽수");
for (int[] location : deciduousLocations) {
d.display(location[0], location[1]);
}
for (int[] location : coniferLocations) {
c.display(location[0], location[1]);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 낙엽수의 위치 1, 1
// 나무에 잎이 없습니다!
// 낙엽수의 위치 33, 50
// 나무에 잎이 없습니다!
// 낙엽수의 위치 100, 90
// 나무에 잎이 없습니다!
// 침엽수의 위치 10, 87
// 침엽수의 위치 24, 76
// 침엽수의 위치 2, 64
수천개의 나무를 심으면 수천개의 Tree 인스턴스를 생성해야 했던 것과 달리 이제 침엽수가 필요하면 침엽수의 인스턴스 1개를 생성하고, 낙엽수가 필요하면 낙엽수 인스턴스 1개를 생성하면 된다. 또 다시 침엽수가 필요하다면 이미 만들어진 인스턴스를 통해 심을 수 있다. 모든 나무의 상태를 클라이언트가 관리하도록 했다.
이제 실행 시에 객체 인스턴스의 개수가 대폭 줄어들었기 때문에 메모리를 훨씬 절약할 수 있다. 또한 여러 가상 객체의 상태를 한곳에서 관리할 수 있어졌다. 특정 클래스의 인스턴스가 아주 많이 필요하지만 모두 같은 방식으로 제어해야 할 때 사용하면 유용한 패턴이다. 단, 다르게 제어할 수는 없다는 단점이 있다.
- Flyweight : 공통된 메서드를 정의 (Factory에 제공하는 인터페이스)
- ConcreteFlyweight : Flyweight 인터페이스의 서브 클래스 (실제로 사용될 객체)
- FlyweightFactory : Flyweight의 인스턴스를 생성, 공유(캐싱)
- Client : Factory를 이용해 Flyweight 객체를 제공받음
* 아래의 자료들을 참고하였습니다.
에릭 프리먼 · 엘리자베스 롭슨 · 케이시 시에라 · 버트 베이츠, 『헤드퍼스트 디자인패턴 개정판』, 서환수 옮김, 한빛미디어(2022), p638-639.
GoF 디자인패턴 : Flyweight
장점 : 애플리케이션에서 사용하는 메모리를 줄일수 있다단점 : 코드의 복잡도가 증가한다클라이언트는 Flyweight를 사용하기위해서 FlyweightFactory를 이용해서 실제 객체를 사용하게 된다.출처 : htt
velog.io
'Programming' 카테고리의 다른 글
디자인패턴 시리즈 12. 책임 연쇄 패턴 (chain-of-responsibility Pattern) (0) | 2023.01.25 |
---|---|
디자인패턴 시리즈 11. 프로토타입 패턴 (prototype Pattern) (0) | 2023.01.24 |
디자인패턴 시리즈 9. 브리지 패턴 (Bridge Pattern) (0) | 2023.01.24 |
디자인패턴 시리즈 8. 상태 패턴 (State Pattern) (0) | 2023.01.23 |
디자인패턴 시리즈 7. 빌더 패턴 (Builder Pattern) (0) | 2023.01.19 |
댓글