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 | 지연 로딩 + 캐시 (프록시 패턴) |
---|
최신 데이터 | X | O | △ |
메모리 사용량 | 최대 | 최소 | 중간 (사용한 것에 대해 캐싱으로 들고 있음) |
실행 속도 병목점 | 생성 시점 | 사용할 때 마다 | 알기 어려움 (처음 사용한 시점에 발생) |
- 프록시 패턴의 경우 명확하지 않다는 문제가 있다.
요즘 세상에서의 프록시 패턴
- 메모리량이 많다.
- 한 번에 그리는 이미지 수가 많지 않다면?
- 필요할 때마다 디스크에서 읽자.
- 충분히 빨라졌다.
- 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
subway surfers online I'm captivated by how this article seamlessly weaves together seemingly unrelated topics into a cohesive narrative.