2011년 5월 23일 월요일

The Flyweight Pattern

11. The Flyweight Pattern
Flyweight 패턴은 유사한 클래스간의 오버헤드를 피하기 위해 사용되는 패턴이다. 때때로 개발자는 데이터를 표현하기 위해 매우 많은 수의 소규모 클래스를 생성해야 한다. 만약 해당 인스턴스들이 일부 파라미터를 제외하면, 근본적으로는 같은 성질의 것을 결정할 수 있을 경우 개발자는 인스턴스화해야 하느 stj로 다른 클래스들의 수를 획기적으로 줄일 수 있게 된다. 만약 개발자가 클래스 인스턴스의 외부에 존재하는 이런 변수들(외제적인 state들)을 움직여서 해당 변수들을 메소드 호출의 일부분으로 전달할 수 있다면, 개발자는 해당 변수들을 공유함으로 써 개별적인 인스턴스들의 수를 획기적으로 줄일 수 있게 된다.
Flyweight 패턴은 이런 클래스들을 관리하는 방법을 제공한다. 이 패턴은 해당 인스턴스를 독보적인 존재로 만들어주는 인스턴스의 고유한 데이터와 아규먼트로 전달 되는 부수적인 데이터를 참조한다.
구조
*


역할
  • Flyweight 의 역할 : Flyweight가 외재적인 state를 주고 받을 인터페이스를 선언한다.
  • Concrete Flyweight : Flyweight 인터페이스를 구현하고, 필요하다면 내재적인 state를 저장할 공간을 추가한다. Concrete Flyweight 오브젝트는 공유가 가능해야 한다. 직접 저장하고 있는 state는 내재적인 것이어야 한다. 즉, Concrete Flyweight의 컨텍스트와는 무관한 것이어야 한다.
  • Unshared Concrete Flyweight : Flyweight의 모든 서브클래스가 공유가능해야하는 것은 아니다. Flyweight의 인터페이스는 단지 공유를 가능하게 할 뿐이지, 강제하는 것은 아니다. Flyweight 패턴의 오브젝트 구조에서는 Unshared Concrete Flyweight 오브젝트가 Concrete Flyweight 오브젝트를 child로써 가지고 있는 것이 일반적이다.
  • Flyweight Factory : Flyweight 오브젝트를 생성하고 관리한다. Flyweight 오브젝트가 적절히 공유됨을 보장한다. 클라이언트가 flyweight 오브젝트를 요구하면, Flyweight Factory가 해당 flyweight의 이미 존재하는 오브젝트나 없다면 새것을 만들어서 공급한다.
  • Client : Flyweight 오브젝트들에 대한 reference를 유지한다. Flyweight에 대한 외재적인 state를 계산하고 저장한다.
    참고사항
    Flyweight가 동작하기 위한 근간이 되는 state들은 내재적인지 외재적인지 구별되어야 한다. 내재적인 state는 Concrete Flyweight 오브젝트 안에 저장되고, 내재적인 state는 클라이언트 오브젝트가 계산하거나 저장한다. 클라이언트는 flyweight의 operation을 호출 할 때 이 외재적인 state를 전달해야 한다.
    클라이언트는 Concrete Flyweight를 직접적으로 인스턴스화하지 않는다. Flyweight 오브젝트들이 적절하게 공유됨을 보장하기 위해서 클라이언트는 Flyweight Factory를 통해서만 Concrete Flyweight 오브젝트를 얻어야 한다.


    의도
    많은 수의 작은 오브젝트들을 사용할 때, 공유기법을 사용하여 효율성을 높인다.
    적용시기
    Flyweight 패턴의 효율성은 어떻게, 어디서 사용되는지와 밀접한 관련이 있다. 아래의 모든 조건을 만족하는 경우에만 Flyweight 패턴을 사용하라.
    l 어플리케이션이 많은 수의 오브젝트를 사용한다.
    l 급격히 늘어나는 오브젝트의 수 때문에 storage 비용이 높다.
    l 오브젝트 state의 대부분을 외재적으로 만들 수 있다.
    l 일단 외재적인 state만 제거되고 나면 여러 오브젝트의 그룹들을 몇 개의 공유 오브젝트로 대체할 수 있다.
    l 어플리케이션의 구현이 object identity에 의존하지 않는다. Flyweight 오브젝트는 공유될 수 있기 때문에, 개념적으로는 구별되어야 할 오브젝트 간에 object identity가 같을 수 있다.
    결론
    Flyweight 패턴을 사용하면 외재적인 state( 특히 이 state가 전에는 내재적이었을 경우 )를 옮기고, 찾아내고, 계산하는 데에 소요되는 run-time cost가 발생하게 된다. 그러나, 이러한 비용은 공간 절약에 의해 상쇄되며, 이 공간 절약은 flyweight가 더 많이 공유될 수록 늘어난다.
    공간 절약의 정도는 다음 요인들에 의해 결정된다.
    l 공유를 통한 인스턴스 총개수의 감소
    l 오브젝트 별 내재적인 state의 양
    l 외재적인 state가 매번 계산되는지 저장되는지의 여부
    더 많은 flyweight가 공유될 수록 더 많은 공간이 절약되는 것이다. 그리고 공유되는 state(즉, 내재적인 state)의 양이 클 수록 많이 절약되는 것이다. 오브젝트가 내/외재적인 state를 모두 실재적인 양 만큼만 사용하고, 외재적인 state는 저장되기 보다 매번 새로 계산할 때 공간 절약이 최대가 된다. 이렇게 하면 두 가지 측면에서 공간을 절약하는 것이다. 공유를 통해 내재적인 state를 저장하는 공간을 절약하고, 외재적인 state를 계산하는 시간이 들어가는 대신에 이를 저장할 공간을 절약하게 된다.
    Flyweight 패턴은 leaf node를 공유하는 그래프와 같은 계층 구조를 표현하기 위해 Composite 패턴과 자주 결합된다. 그러나 공유를 하기 때문에 flyweight leaf node는 자신의 parent에 대한 reference를 유지하기 보다는, 외재적인 state의 일부로써 parent reference가 flyweight에 전달된다.
    Flyweight 패턴을 구현할 때 고려할 사항
    l 외재적인 state의 제거. 외재적인 state를 분리해내고 공유할 오브젝트로 부터 이들을 제거하기가 얼마나 쉬운지에 따라 Flyweight 패턴을 적용할지 말지가 결정된다. 만약 공유하기 전의 오브젝트 개수만큼이나 많은 다른 종류의 외재적인 state가 존재한다면 이들을 제거하는 것이 공간 비용을 절약하는데에 전혀 도움이 되지 않을 수 있다. 이상적으로는 외재적인 state는 상대적으로 훨씬 적은 공간만을 차지하는 별도의 오브젝트 구조로 부터 계산될 수 있어야 한다.
    l 공유 오브젝트 관리하기. 오브젝트들이 공유되기 때문에 클라이언트가 그들을 직접적으로 인스턴스화해서는 안 된다. Flyweight Factory가 클라이언트에게 특정 flyweight를 지정해 준다. Flyweight 오브젝트는 클라이언트가 원하는 flyweight 오브젝트를 찾을 수 있게하는 집합적인 저장 공간을 사용하는 것이 보통이다
    공유 가능이라는 특성은 어떤 형태의 reference counting이나 garbage collection을 할 수 있음을 의미하기도 한다. 그러나, flyweight의 개수가 작은 고정수라면 이런 것들은 필요없다. 이런 경우에는 만들어진 flyweight를 계속 유지하는 것이 더 유리하다.


    예제소스
    *


    예제 소스
    big-.txt
    ................
    ................
    ................
    ................
    ..##########....
    ................
    ................
    ................
    big0.txt
    ....######......
    ..##......##....
    ..##......##....
    ..##......##....
    ..##......##....
    ..##......##....
    ....######......
    ................
    big1.txt
    ......##........
    ..######........
    ......##........
    ......##........
    ......##........
    ......##........
    ..##########....
    ................
    big2.txt
    ....######......
    ..##......##....
    ..........##....
    ......####......
    ....##..........
    ..##............
    ..##########....
    ................
    big3.txt
    ....######......
    ..##......##....
    ..........##....
    ......####......
    ..........##....
    ..##......##....
    ....######......
    ................
    big4.txt
    ........##......
    ......####......
    ....##..##......
    ..##....##......
    ..##########....
    ........##......
    ......######....
    ................
    big5.txt
    ..##########....
    ..##............
    ..##............
    ..########......
    ..........##....
    ..##......##....
    ....######......
    ................
    big6.txt
    ....######......
    ..##......##....
    ..##............
    ..########......
    ..##......##....
    ..##......##....
    ....######......
    ................
    big7.txt
    ..##########....
    ..##......##....
    ..........##....
    ........##......
    ......##........
    ......##........
    ......##........
    ................
    big8.txt
    ....######......
    ..##......##....
    ..##......##....
    ....######......
    ..##......##....
    ..##......##....
    ....######......
    ................
    big9.txt
    ....######......
    ..##......##....
    ..##......##....
    ....########....
    ..........##....
    ..##......##....
    ....######......
    ................
    import java.io.BufferedReader;
    import java.io.FileReader;
    import java.io.IOException;
    public class BigChar {
    private char charname;
    private String fontdata;
    public BigChar(char charname) {
    this.charname = charname;
    try {
    BufferedReader reader = new BufferedReader(
    new FileReader("big" + charname + ".txt")
    );
    String line;
    StringBuffer buf = new StringBuffer();
    while ((line = reader.readLine()) != null) {
    buf.append(line);
    buf.append("\n");
    }
    reader.close();
    this.fontdata = buf.toString();
    } catch (IOException e) {
    this.fontdata = charname + "?";
    }
    }
    public void print() {
    System.out.print(fontdata);
    }
    }
    import java.util.Hashtable;
    public class BigCharFactory {
    private Hashtable pool = new Hashtable();
    private static BigCharFactory singleton = new BigCharFactory();
    private BigCharFactory() {
    }
    public static BigCharFactory getInstance() {
    return singleton;
    }
    public synchronized BigChar getBigChar(char charname) {
    BigChar bc = (BigChar)pool.get("" + charname);
    if (bc == null) {
    bc = new BigChar(charname);
    pool.put("" + charname, bc);
    }
    return bc;
    }
    }
    public class BigString {
    private BigChar[] bigchars;
    public BigString(String string) {
    bigchars = new BigChar[string.length()];
    BigCharFactory factory = BigCharFactory.getInstance();
    for (int i = 0; i < bigchars.length; i++) {
    bigchars[i] = factory.getBigChar(string.charAt(i));
    }
    }
    public void print() {
    for (int i = 0; i < bigchars.length; i++) {
    bigchars[i].print();
    }
    }
    }
    public class mainClass {
    public static void main(String[] args) {
    if (args.length == 0) {
    System.out.println("사용 예: java mainClass 011-9954-1682");
    System.exit(0);
    }
    BigString bs = new BigString(args[0]);
    bs.print();
    }
    }


    관련패턴
    • Proxy : Flyweight 패턴에서는 인스턴스를 생성하는데 시간이 걸리는 경우 인스턴스를 공유해서 처리 스피드를 빠르게 할 수 있습니다. Proxy 패턴에서는 대리인을 내세워 처리 스피드를 빠르게 합니다.
    • Composite : Flyweight 패턴을 사용해서 composite 패턴의 leaf 역할을 공유시킬 수 있는 경우가 있다.
    • Singleton : FlyweightFactory 역할은 singleton 패턴이 되는 경우가 있다. 또한 singleton 패턴의 singleton 역할은 하나의 인스턴스만 만들기 때문에 그 인스턴스가 사용되는 모든 곳에서 공유를 하게 됩니다. Singleton 역할의 인스턴스는 intrinsic한 정보만 가지고 있습니다.

댓글 없음:

댓글 쓰기

블록체인 개요 및 오픈소스 동향

블록체인(block chain) 블록체인은 공공 거래장부이며 가상 화폐로 거래할때 발생할때 발생할 수 있는 해킹을 막는 기술. 분산 데이터베이스의 한 형태로, 지속적으로 성장하는 데이터 기록 리스트로서 분산 노드의 운영자에 의한 임의 조작이 불가...