티스토리 뷰

반응형

안녕하세요 Pingu입니다.🐧

 

지난 글에서는 구조 패턴 중 Facade Pattern(퍼사드)에 대해 알아봤는데요, 이번 글에서는 계속해서 구조 패턴 중 하나인 Flyweight Pattern(플라이 웨이트)에 대해 알아보도록 하겠습니다.

플라이 웨이트 패턴이란?

Flyweight 패턴은 메모리 사용량과 처리를 최소화하기 위한 디자인 패턴입니다. Flyweight 패턴은 각 객체의 모든 데이터를 유지하는 대신 여러 객체 간에 state 공통부분을 공유하여 메모리에 더 많은 객체를 넣을 수 있는 구조적 디자인 패턴입니다.

 

Flyweight 패턴은 모든 객체가 기본 데이터를 공유하여 메모리를 절약하게 되는데요, 데이터 공유를 위해 이러한 객체들은 보통 immutable(변경 불가능) 합니다. 플라이 웨이트 패턴에는 Flyweight라는 객체와 이를 반환하는 static 메서드가 존재합니다. 어디선가 본 것 같은 느낌이 드신다면 예전에 알아본 생성 패턴의 Singleton 패턴을 변형한 패턴으로 볼 수도 있기 때문입니다.

  • Flyweight
    • 공유할 수 있는 정보를 갖는 플라이 웨이트 객체를 정의합니다.
  • Flyweight Factory
    • Flyweight 객체를 만들고 관리합니다.
    • Flyweight의 공유 정보가 올바르게 공유되도록 합니다.
    • 클라이언트가 Flyweight 객체를 요청하면 팩토리가 이전에 만들어 놓은 동일한 Flyweight 객체가 있는지 찾아보고 없다면 새로 생성합니다.
  • Concrete Flyweight
    • Flyweight 인터페이스를 구현하고 공유 상태에 대한 저장공간을 확보합니다.
    • 여기에 저장하는 상태들은 intrinsic state(고유한 상태)라고 합니다.
  • Unshared Concrete Flyweight (Context)
    • 이 클래스는 Concrete Flyweight 객체를 자식으로 갖습니다.
    • 모든 Flyweight 서브 클래스를 공유할 필요는 없습니다.
  • Client
    • Flyweight에 대한 참조를 유지합니다.
    • Flyweight 객체의 각각의 상태를 처리하거나 저장합니다.

플라이 웨이트 패턴은 언제 쓰나요?

생성하는데 자원이 많이 필요하고 생성 비용을 최소화할 수 없는 경우 가장 좋은 방법은 객체를 한 번만 만드는 방법인데요, 싱글톤을 사용하지만 구성이 다른 여러 개의 공유 인스턴스가 필요할 때 플라이 웨이트를 사용할 수 있습니다.

 

예를 들어 FPS 게임에서 총을 쏠 때 나오는 총알을 생각해보겠습니다. 총알을 만약 각각 하나의 객체로 모두 독립적으로 생성한다면, 많은 총알이 발사될 때 메모리를 많이 차지해서 성능이 나빠질 수 있습니다.

 

사실 수많은 총알은 위치만 달라질 뿐 서로 가지고 있는 특징들은 모두 같습니다. 총알의 색, 크기 등은 모두 같은데 이를 모든 총알 객체가 독립적으로 가지고 있어야 할까요? 이럴 때 플라이 웨이트 패턴을 사용하여 메모리를 절약할 수 있습니다.

 

방금 예시로 든 총알 객체의 경우 위치, 속도는 각각의 총알이 모두 각자 가지고 있는 고유한 정보입니다. 이러한 정보를 intrinsic state라고 합니다. 그리고 총알의 색, 크기와 같은 모두 같은 정보를 extrinsic state라고 합니다. intrinsic state(속도, 좌표 등)는 각각의 총알 객체만 수정할 수 있고 extrinsic state(색, 총알 크기)는 총알 공장에서 만들어주는 대로 계속 유지해야 하는 정보입니다.

 

즉 Flyweight 패턴은 extrinsic state를 각각의 객체에 저장하는 것이 아닌 외부에 공유할 수 있는 상태로 저장하여 메모리를 절약하는 거예요. Flyweight 객체에 공유할 수 있는 정보를 저장하여 객체에서는 이를 가져다 쓰게 됩니다. 이 경우 동일한 플라이 웨이트 객체를 서로 다른 객체가 사용할 수 있기 때문에 플라이 웨이트 객체는 immutable 하게 만들어져서 한 번 만들어지면 변하지 않습니다.

 

이러한 Flyweight 객체를 쉽게 만들기 위해 팩토리 메서드 패턴을 사용합니다. 찾고자 하는 Flyweight 객체를 찾고 있다면 이를 반환해주고 없다면 새로 만들어서 반환해주면 됩니다.

 

플라이 웨이트 패턴의 결과

장점

  • 앱에 유사한 객체가 많다면 메모리를 절약할 수 있습니다.

단점

  • Flyweight 메서드를 호출할 때마다 존재하는 데이터 검색 등 런타임 비용이 발생할 수 있습니다. 이는 메모리 절약으로 발생하는 문제이므로 상쇄되긴 하지만 Flyweight 객체가 많이 존재하게 될수록 비용이 증가합니다.
  • 코드가 복잡해집니다.

 

예제

그럼 이제 Flyweight Pattern을 Swift로 구현해보겠습니다.

아까 예로 들어본 총알 예제를 한 번 구현해볼게요.

 

가장 먼저 Flyweight 클래스와 이를 갖는 Bullet 클래스를 만들어야 할 것 같아요.

// Flyweight
class BulletFlyweight {
    let color: String
    let size: Float
    
    init(color: String, size: Float) {
        self.color = color
        self.size = size
    }
}

Flyweight 객체의 값은 한 번 만들어지면 변경 할 수 없게 let으로 선언했습니다.

// Unshared Concrete Flyweight
class Bullet {
    var x: Float
    var y: Float
    var velocity: Float
    private var extrinsicState: BulletFlyweight
    
    init(x: Float, y: Float, velocity: Float, extrinsicState: BulletFlyweight) {
        self.x = x
        self.y = y
        self.velocity = velocity
        self.extrinsicState = extrinsicState
    }
    
    func getState() {
        print("Flyweight : \(x),\(y),\(velocity) 값을 갖는 \(extrinsicState.color), \(extrinsicState.size) 총알")
    }
}

저는 extrinsicState에는 접근할 수 없도록 만들어줬습니다.

 

그리고 클라이언트가 Bullet을 만들 때 필요한 Flyweight 객체를 반환해줄 FlyweightFactory 클래스도 만들어보겠습니다.

// Flyweight Factory
class BulletFlyweightFactory {
    static private var bulletFlyweightList: [BulletFlyweight] = []
    static func getBulletFlyweight(color: String, size: Float) -> BulletFlyweight {
        if let flyweightIndex = self.bulletFlyweightList.firstIndex(where: { (bullet) -> Bool in
            return bullet.color == color && bullet.size == size
        }) {
            return self.bulletFlyweightList[flyweightIndex]
        } else {
            self.bulletFlyweightList.append(BulletFlyweight(color: color, size: size))
            print("\(color),\(size) Flyweight 객체 생성")
            return self.bulletFlyweightList.last ?? BulletFlyweight(color: color, size: size)
        }
    }
    static var flyweightCount: Int {
        return self.bulletFlyweightList.count
    }
    private init() {}
}

위의 코드를 보면 요구하는 Flyweight 객체가 이미 만들어 놓은 객체인지 검색하고 만약 만들어놓았다면 해당 객체를 반환하고 그렇지 않다면 새로 만들어서 리스트에 추가한 뒤 반환합니다.

또한 싱글턴 디자인으로 만들어 프로그램에서 한 번만 만드는 것을 보장했습니다.

 

이렇게 하고 직접 사용해보면 아래와 같은 결과를 얻을 수 있습니다.

위의 결과를 보면 각각 동일한 Flyweight 객체를 공유하는 5개의 총알을 만드는 코드인데요, Flyweight 객체는 2번만 만드는 것을 볼 수 있습니다. 즉 10번을 만들지도 않고 10개의 데이터를 유지하는 것이 아닌 2개의 데이터만 만들고 이를 공유하는 것이죠. 이런 식으로 메모리를 절약할 수 있습니다.

 

이렇게 구조 패턴 중 하나인 Flyweight Pattern에 대해 알아보고 간단하게 구현도 해봤습니다.

혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다!

 

전체 코드는 여기에서 볼 수 있습니다.

 

감사합니다.

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함