반응형
의도
- 객체를 가볍게 만들어 메모리 사용을 줄이는 패턴
- 공유(sharing)를 통해 많은 수의 소립(fine-grained) 객체들을 효과적으로 지원한다.
- 문서편집기에서 문자 단위를 객체로 처리하게 된다면 응용력이 높아질 수는 있으나 엄청난 비용이 들어간다. 적은 분량의 문서라고 할지라도 수백, 수천 개의 문자 객체를 포함할 것이고 이는 엄청난 메모리를 차지하며 예상하지 못한 실행 시간 낭비를 가져올 수 있다.
- 플라이급은 이러한 문제를 객체 공유를 통해 해결할 수 있다.
장점
- 애플리케이션에서 사용하는 메모리를 줄일 수 있다.
단점
- 코드의 복잡도가 증가한다.
JAVA 사용 예
- Integer의 valueOf(int i)
- -128~127 범위의 수 입력 시, 캐싱하고 재호출 시 캐싱된 객체 제공
활용성
- 응용프로그램이 대량의 객체를 사용해야 할 때
- 객체의 수가 너무 많아져 저장 비용이 너무 높아질 때
- 대부분의 객체 상태를 부가적인 것으로 만들 수 있을 때
- 자주 변하는 속성들을 제거한 후 객체들을 조사해 보니 객체의 많은 묶음이 비교적 적은 수의 공유된 객체로 대체될 수 있을 때. 즉, 서로 다른 객체로 간주한 이유가 이들 자주 변하는 속성 때문이었고 본질이 달랐던 것은 아닐 때
- 응용프로그램이 객체의 정체성에 의존하지 않을 때.
- 플라이급 객체들은 공유될 수 있음을 의미하는데, 식별자가 있다는 것은 서로 다른 객체로 구별해야 한다는 의미이므로 플라이급 객체를 사용할 수 없다.
결과
- 플라이급 패턴은 예전에는 모두 본질적인 상태로 저장되어 있던 것을 부가적인 상태로 만든다.
- 부가적인 상태의 연산과 전송에 드는 런타임 비용이 생길 수 있다. 하지만 플라이급 객체의 공유를 통해 저장소 절약이라는 이점을 얻을 수 있다.
- 저장소 절약은 기능적이다.
- 공유해야 하는 인스턴스의 전체 수를 줄일 수 있다.
- 객체별 본질적 상태의 양을 줄일 수 있다.
- 부가적인 상태는 연산되거나 저장될 수 있다.
- 더 많은 Flyweight가 공유될수록 저장소가 절약된다. 공유할 상태가 많아질수록 절약된다. 대부분의 본질적인 상태가 저장되고 부가적인 상태는 연산될 때 절약의 효과가 가장 크다. 이때 부가적인 상태의 연산과 전송에 드는 비용이 가장 크게 발생한다.
- 플라이급 패턴은 복합체 패턴과 조합하여 그래프와 같이 계층적 구조를 모델링하는데 사용한다.
- 이렇게 하면, 플라이급 단말 노드들은 자신의 부모에 대한 포인터를 저장할 수 없기 때문에, 부모 포인터를 부가적 상태의 일부로서 플라이급 객체에다가 매개변수로 전달해야한다.
협력 방법
- 플라이급 객체가 기능을 수행하는 데 필요한 상태가 본질적인 것인지 부가적인 것인지 구분해야 한다.
- 본질적 상태는 ConcreteFlyweight에 저장, 부가적인 상태는 사용자가 저장하거나 연산되어야 하는 다른 상태로 관리해야 한다.
- 사용자는 ConcreteFlyweight의 인스턴스를 직접 만들 수 없다. 사용자는 ConcreteFlyweight 객체를 FlyweightFactory 객체에서 얻어야 한다. 이렇게해야 플라이급 객체가 공유될 수 있다.
구현 고려사항
- 자주 변하는 속성(부가적 상태)를 제외한다
- 부가적 상태란 플라이급 객체가 사용될 상황에 따라 달라질 수 있고, 그 상황에 종속적인 것을 의미한다.
- 본질적 상태란 플라이급 객체에 저장되어야 하며, 상황과 상관없이 본질적 특성 정보들이 객체를 구성한다.
- 공유할 객체를 관리한다
구조
- 플라이급 객체 공유 방법
실제 구현 구조
소스코드
//value(텍스트)과 color(텍스트 색깔)은 변하지않는 본질적인 속성이 아니라 자주 변하는 속성이라 보았다.
public class Character {
private char value;
private String color;
//Flyweight 객체 참조
private Font font;
public Character(char value, String color, Font font) {
this.value = value;
this.color = color;
this.font = font;
}
}
//FlyweightFactory
public class FontFactory {
//Flyweight Pool
private Map<String, Font> cache = new HashMap<>();
public Font getFont(String font) {
if(cache.containsKey(font)) {
return cache.get(font);
} else {
String[] split = font.split(":");
//0 : 폰트 이름, 1 : 폰트 사이즈
Font newFont = new Font(split[0], Integer.parseInt(split[1]));
cache.put(font, newFont);
return newFont;
}
}
}
//플라이급 객체는 불변성을 가져야한다. 변경되면 모든 것에 영향을 준다.
//여기서는 family와 size를 본질적인 속성으로 보았다.
//Flyweight
public final class Font {
private final String family;
private final int size;
public Font(String family, int size) {
this.family = family;
this.size = size;
}
public String getFamily() {
return family;
}
public int getSize() {
return size;
}
}
public class Client {
public static void main(String[] args) {
FontFactory fontFactory = new FontFactory();
Character c1 = new Character('f', "black", fontFactory.getFont("nanum:12"));
Character c2 = new Character('l', "white", fontFactory.getFont("nanum:12"));
Character c3 = new Character('y', "red", fontFactory.getFont("nanum:12"));
Character c4 = new Character('w', "yellow", fontFactory.getFont("nanum:12"));
Character c5 = new Character('e', "white", fontFactory.getFont("nanum:12"));
Character c6 = new Character('i', "green", fontFactory.getFont("nanum:12"));
Character c7 = new Character('g', "white", fontFactory.getFont("nanum:12"));
Character c8 = new Character('h', "red", fontFactory.getFont("nanum:12"));
Character c9 = new Character('t', "black", fontFactory.getFont("nanum:12"));
}
}
관련 패턴
- 플라이급은 복합체 패턴과 함께 사용된다.
- 공유되는 단말 노드를 갖는 방향성 비순환 그래프 형태를 써서 논리적으로 계층 구조를 구현하는 것이 여기에 해당된다.
- 상태 패턴 또는 전략 패턴을 플라이급 객체로 구현할 수 있다.
[참고자료]
리처드 헬름, 랄프 존슨, 존 블리시디스, 『GoF의 디자인 패턴 : 재사용성을 지닌 객체지향 소프트웨어의 핵심요소』, 김정아 번역, 프로텍미디어(2015)
http://www.cs.unc.edu/~stotts/GOF/hires/pat4efso.htm
반응형