본문 바로가기
Programming

디자인패턴 시리즈 10. 플라이웨이트 패턴 (Flyweight Pattern)

by LeeJ1Hyun 2023. 1. 24.

플라이웨이트 패턴 (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

 

댓글