Java/Design Pattern

[디자인 패턴] 구조 패턴 - 플라이웨이트 패턴 (Flyweight Pattern)

민돌v 2023. 3. 15. 20:12
728x90
(인프런) 코딩으로 학습하는 GoF의 디자인 패턴 - 백기선, 강의를 보고 정리한 글입니다.
코드는 GitHub 에 있습니다

 

#1. 객체 생성 관련 패턴

#2. 구조 관련 패턴

 

#3. 행동 관련 패턴

 

 

 


✔️ 플라이웨이트 패턴 이란 (Flyweight Patterns)

flyweight 는 가볍다는 뜻을 가지고 있습니다.
  • 플라이웨이트 패턴은, 객체를 가볍게 만들어 메모리 사용을 줄인다는 목적을 가지는 패턴입니다.
  • 플라이웨이트 패턴은, 자주 변하는 속성과 변하지 않는 속성을 분리하고 재사용하여 메모리 사용을 줄이는 구조를 설계하는 패턴입니다.
  • 주로 굉장히 많은 인스턴스를 가지고 있는 어플리케이션에 사용되는 패턴입니다.
  • 인스턴스가 많으면 메모리를 많이 잡게되고 OOM 즉, Out Of Memory 문제가 발생할 수 있기에 이를 예방하기 위한 패턴입니다.

플라이웨이트

→ 요점은 메모리를 줄이는 것인데, 주로 캐싱을 사용합니다.

 


✔️ 플라이웨이트 패턴 예시

  • 플라이웨이트에서 중요한것은 변하지 않는 속성을 분리하는 것. 입니다.
  • 아래 처럼 Character 라는 객체에, font 의 종류와 사이즈가 똑같은게 종종 사용된다고 느껴져 미리 만들어 (캐싱) 메모리를 아끼고 싶다! 
  • 는 생각이 든다면 플라이웨이트 패턴을 적용하기 좋은 상태입니다.

 

public class Client {

    public static void main(String[] args) {
        Character c1 = new Character('h', "white", "Nanum", 12);
        Character c2 = new Character('e', "white", "Nanum", 12);
        Character c3 = new Character('l', "white", "Nanum", 12);
        Character c4 = new Character('l', "white", "Nanum", 12);
        Character c5 = new Character('o', "white", "Nanum", 12);
    }
}

public class Character {

    private char value;
    private String color;
    private String fontFamily;
    private int fontSize;

    public Character(char value, String color, String fontFamily, int fontSize) {
        this.value = value;
        this.color = color;
        this.fontFamily = fontFamily;
        this.fontSize = fontSize;
    }
}

 

플라이웨이트 패턴 적용

  • 아래처럼 우선 Font 에 대한 정보를 객체로 추출했습니다.
  • 이제 중요한 것은 캐싱이겠죠
//자주 바뀌는 객체와 안바뀌는 객체를 판단하는게 중요함
public class Character {

    private char value;
    private String color;
    private Font font;

    public Character(char value, String color, Font font) {
        this.value = value;
        this.color = color;
        this.font = font;
    }
}


/**
 *   flyweight 객체
 *  flyweight 객체는 불변한 객체이야 한다. → 변하지 않는 객체로 판단해서 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;
    }
}

 

플라이웨이트 캐싱 - 팩토리

  • 팩토리라 이야기하지만, 캐싱의 방법이야 저는 어떻게 구현하든 상관없다는 생각이 들었습니다.
  • 강의 예제에서는, 마치 데이터베이스인 것 처럼 Map 을 이용하여 우선 사용하고자 하는 폰트가 Map 에 존재하면 꺼내서 같은 객체를 추출하고, 
  • 없다면 생성해 Map 에 저장후 생성한 객체를 반환해 주었습니다.
/**
 *  flyweight factory
 *
 *  flyweight 갹체를 캐싱해서 가져오는 역할
 */
public class FontFactory {

    Map<String, Font> cache = new HashMap<>();

    public Font getFont(String font) {
        if (cache.containsKey(font)) {
            return cache.get(font);
        }

        String split[] = font.split(":");
        Font newFont = new Font(split[0], Integer.parseInt(split[1]));

        cache.put(font, newFont);
        return newFont;
    }
}

 

플라이웨이트 캐싱 -  클라이언트

  • 이렇게 된다면 클라이언트에서 Font 는 항상 캐싱처리를 하고있는 FontFactory를 통해 불러오고
  • 아래의 예시에서는 나눔 - 12 size 의 font 를 처음 호출할때만 생성하고 이후에는 계속 같은 객체를 반환해서 메모리를 아낄 수 있겠죠
public class Client {

    public static void main(String[] args) {
        FontFactory fontFactory = new FontFactory();

        Character c1 = new Character('h', "white", fontFactory.getFont("nanum:12"));
        Character c2 = new Character('e', "white", fontFactory.getFont("nanum:12"));
        Character c3 = new Character('l', "white", fontFactory.getFont("nanum:12"));
    }
}

 

📌 플라이웨이트 주의할 점

  • 위에서 의아한함을 느꼈을 수 도 있지만, 캐싱 후 같은 객체를 넘겨주기 때문에 이 Flyweight 로 추출한 객체는 불변해야합니다.
  • 불변 객체가 아니라면, 어디서 어떻게 데이터가 변경이 일어나는지 도저히 알 수 없고 참조하고 있는 모든 부분에서의 데이터가 어그러지기 때문입니다.
  • 위에서 Font 객체를 final 로 정의한 이유입니다. 
public final class Font {
    private final String family;
    private final int size;

    public Font(String family, int size) {
        this.family = family;
        this.size = size;
    }
}

 


✔️ JAVA 에서의 플라이웨이트 예시

1) Java Integer -valueOf

  • JAVA Integer 참조 객체에서 valueOf 메소드에서 캐싱을 사용하고 있습니다.
  • 일종의 플라이웨트 패턴이죠

Integer valueOf

 

📌 따라서 캐싱이 되어있는 128까지의 정수는 호출시 같은 객체를 반환해 == 연산에 같은 주소를 반환하는 같은 레퍼런스이기 때문에 true 지만 129부터는 false 가 됩니다.

//true
@Test
void test() {
    Integer a = Integer.valueOf(100);
    Integer b = Integer.valueOf(100);

    System.out.println("a = b " + (a == b)) ;
}

//false
@Test
void test() {
	Integer a = Integer.valueOf(129);
    Integer b = Integer.valueOf(129);

    System.out.println("a = b " + (a == b)) ;
}

 

 


2) String Context Pool

다른 언어에서는 모르겠지만, Java의 String 은 다른 Reference Type 들과 다르게 Context Pool 을 이용하는 독특한 특징을 가집니다.

참고! → [Java/클린 코딩 (with OOP)] - 5. JAVA 기본 타입 vs 참조 타입 (with 래퍼클래스를 사용해야할 때) ➡️ Integer(Wrapper Class) 보다 int(기본 타입)

 

 

 

이상 플라이웨이트 패턴 끝입니다!

 

 

 

 

반응형