Proxy

최완식·2023년 1월 3일
0
post-thumbnail

Proxy 패턴은 뭘까? 서버에서 들었던 것 같은데, 패턴으로는 어떤 의미가 있는지 알아보자.

Proxy

  • 예전에 의미있었던 방법
  • 오히려 클래스가 명백하지 않게 보이는 경우가 있어 조심해야 함

Proxy란?

  • 웹 브라우저 설정에 프록시 서버라는 것이 있음
  • 실제 웹 사이트 서버와 사용자 사이에 있는 중간 서버를 말함
  • 인터넷 상의 캐시 메모리

Proxy Pattern

  • Proxy 서버가 하려는 것과 비슷함
  • 클래스에서 어떤 상태를 유지하는게 애매한 경우가 있다.
    • 데이터가 너무 커서 미리 읽어두면 메모리가 부족함
    • 개체 생성시 데이터 로드 시간이 꽤 걸림
    • 개체는 만들었으나 내부 데이터를 사용하지 않을 수도 있음
    • 이런 것들을 값비싼 리소스라고 함
  • 목표: "당장은 필요 없지만, 필요하게 될 경우를 대비하여 언제든 로드할 수 있게 하자."
  • 처음 화면같은 것을 로드한다고 할 때, 리소스는 파일 경로만 기억
  • 실제로 화면에 보여질 필요가 있을 경우 로드
  • 그리고 그 정보는 저장해둚(캐싱)

예시: Image data

  • 이미지는 용량이 크고
  • 저장 장치에서 읽어와야 하기 때문에 보통 로딩하는데 오래 걸린다. (병목 현상 발생)
  • 그렇기 때문에 프록시 패턴을 적용하기 적합하다.

Proxy 적용 X

public final class Image {
    private ImageData image;

    // 생성시 이미지를 로드
    public Image(String filePath) {
        this.image = ImageLoader.getInstance().load(filePath);
    }

    public void draw(Canvas canvas, float x, float y) {
        canvas.draw(this.image, x, y)
    }
}
  • filepath를 받자마자 무조건 디스크에서 이미지 데이터를 읽어옴
  • 메모리를 많이 사용한다.
  • 무조건 이미지를 불러오기 때문에 시간도 오래 걸린다.
  • 언제나 문제점은 아니다. 만약 500개 이미지를 모두 사용한다고 한다면 큰 문제는 아닐 수 있다.
  • 다만, 사용 여부가 확률적으로 낮다면 굳이 로드할 필요가 없는 데이터를 로드하는 낭비를 범할 수 있다.

Proxy 적용 O

public final class Image {
    private String filePath;
    private ImageData image;

    public Image(String filePath) {
        this.filePath = filePath;
    }

    public void draw(Canvas canvas, float x, float y) {
        if (this.image == null) {
            this.image = ImageLoader.getInstance().load(this.filePath);
        }

        canvas.draw(this.image, x, y);
    }
}
  • 개체 생성시에는 아무 데이터도 읽지 않음
  • 실제로 사용하는 시점에 캐시 여부를 확인하고 로드를 결정
  • 이렇게 필요한 시점에 늦에 읽어오는 방식을 지연(게으른) 로딩(lazy loading)이라 함
    • 이 반대는 즉시((로딩하고싶어서) 안달이 난) 로딩(eagar loading)

즉시 로딩 vs. 지연 로딩

  • 위에서는 두가지 비교를 했는데, 사실 캐싱 여부까지 적용한다면 3가지 분류로 나눌 수 있다.
즉시 로딩지연 로딩 + 캐시 X지연 로딩 + 캐시
(프록시 패턴)
최신 데이터XO
메모리 사용량최대최소중간 (사용한 것에 대해 캐싱으로 들고 있음)
실행 속도 병목점생성 시점사용할 때 마다알기 어려움 (처음 사용한 시점에 발생)
  • 프록시 패턴의 경우 명확하지 않다는 문제가 있다.

요즘 세상에서의 프록시 패턴

  • 메모리량이 많다.
    • 한 번에 로딩해놔도 큰 문제는 아니다.
  • 한 번에 그리는 이미지 수가 많지 않다면?
    • 필요할 때마다 디스크에서 읽자.
    • 충분히 빨라졌다.
    • SSD면 더 빠르다.
  • 인터넷에서 로딩한다면?
    • 디스크보다 더 오래 걸린다.
    • 그 동안 프로그램이 멈춰있을 수는 없다.

프록시 패턴 + 캡슐화의 문제

  • 캡슐화는 잘 해둚
  • 클라이언트는 사용만 하면 결국 이미지가 로드되어 반환됨
  • 하지만.. 언제 해당 클래스가 느려지는지 알 수 없다.
  • 위에서 보았듯이 프록시 패턴을 사용한 경우 명확하지 않다는 문제가 발생하기 대문이다.
  • 클라이언트 입장에서 해당 클랙스를 어떻게 사용할 지 모르기 때문에,
  • 각 방식에 대해 제어권을 클라이언트 쪽으로 넘겨주는 것이 보다 옳다.
  • 결국 고객의 기준으로 프로덕트를 전달하는 것이 옳기 때문이다.

프록시 패턴의 현대화 예

public final class Image {
    private String filePath;
    private ImageData image;

    public Image(String filePath) { 
        this.filePath = filePath;
    }

    public boolean isLoaded() {
        return this.image != null;
    }

    public void load() {
        if (this.image == null) {
            this.image = ImageLoader.getInstance().load(this.filePath);
        }
    }

    public void unload() {
        this.image = null;
    }

    public void draw(Canvas canvas, float x, float y) {
        canvas.draw(this.image, x, y);
    }
}
  • 생성 시에는 filePath만 받는다.
  • 바깥으로 이미지 로드 여부(isLoaded())를 파악 할 수 있게 열어준다.
  • 직접적으로 load() 함수를 열어주어 제어할 수 있도록 한다.
  • 캐시가 상할 것을 대비하여 unload() 함수도 제공한다.
  • draw() 함수의 경우 image가 있다는 전제하에 작동한다.
  • 이렇게 하면 해당 클래스를 사용하는 클라이언트가 화면에 리소스 파일을 몇개 로드했는지에 대한 정보도 보여줄 수 있다!
    • 원래는 로드 여부를 모르기 때문에 그냥 벙찌고 기다려야 한다.
    • 그럼 강종해버리고 그 앱 안쓰겠지

지연 로딩 주의사항

  • 필요한 곳에 잘 선택하여 사용해야 한다.
  • UX도 고려해야하기 때문
  • 그렇게 되면 프록시 패턴의 입지가 좀 애매해짐
    • 클라이언트 쪽으로 제어여부를 노출하기 때문에 캡슐화가 좀 깨짐
      • 결국 내부가 어떻게 도는지 노출하는 셈
  • 즉, 부수적인 요소(시간, UX 등)을 고려 해야 한다면 캡슐화가 깨짐에도 열어두는 것이 좋은 방안일 수 있다.
  • 단순히 캡슐화를 위해 밖에서 동작을 명확히 알아야 함에도 숨기는 것은 개념에 함몰되어 중요한 것을 놓치는 오류를 범할 수 있다.

Reference

profile
Goal, Plan, Execute.

1개의 댓글

comment-user-thumbnail
2023년 8월 9일

subway surfers online I'm captivated by how this article seamlessly weaves together seemingly unrelated topics into a cohesive narrative.

답글 달기