디자인패턴

[Design Pattern] 프록시(Proxy) 패턴 (feat. 즉시로딩 vs 지연로딩)

쉽코기 2022. 2. 16. 17:41

개발 도중 이미지의 로딩 시점과 캐시에 대해 고민해야할 때가 있습니다.

과거 채팅이 가능한 안드로이드 앱 개발중 시작화면에서 친구목록을 보여줘야할 때가 있었든데

처음에는 서버에서 친구들 사진을 가져왔고  친구가 많아짐에 따라 병목을 경험한 적이 있었습니다.

 

이런 상황에서 유용한 프록시 패턴과 함께 즉시로딩과 지연로딩의 장단점에 대해서 까지 덧붙여 알아보겠습니다.

 

1. 프록시 패턴

프록시(Proxy) 란 사전적으로 "대신" 이라는 의미를 담고 있습니다.

한마디로 어떤 리소스를 직접 접근하지 않고 중간에 프록시 객체가 대신 응답해주는 방식입니다.

이렇게 하면 기본적인 정보를 전달 받고 실질적(무거운) 정보가 반드시 필요한 순간 까지 그 로딩을 미룰 수 있습니다.

 

2. 즉시로딩 VS 지연로딩

 

첫번째 코드는 프록시 패턴을 적용하지 않은 코드 입니다. 생성 즉시 로딩되어 이미지 개채를 들고 있고 언제든 사용(draw) 할 수 있습니다. 

 

두번째 코드는 프록시 패턴을 적용한 코드 입니다. 생성 시점과 이미지를 로딩하는 시점이 다릅니다. 실제로 그려야 되는 그 순간까지 로딩을 미루는 지연로딩(Lazy loading)입니다.

 

즉시로딩의 문제를 생각해 보았을때 이미지를 사용하지 않을 수도 있는데

모든 이미지를 저장하여 리소스를 잡아먹고 

로딩하는데 시간도 오래걸리고 

효율이 떨어질 수 있습니다.

 

 

public 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);
    }
}
public class LazyImage {
    private ImageData image;
    private String filePath;

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

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

 

 

3. 장단점 비교

 

그러하다고 지연로딩 과 프록시 패턴이 모든 상황에서 좋은 것은 아닙니다.

 

위에 예제 코드를 보았을 때 클라이언트 입장에서 이미지를 로드하는지 혹은 이미 존재하는 이미지를 가져다 쓰는지 알 수 가 가 없습니다. 사용자와의 상호작용이 중요해진 요즘은 이러한 방식은 문제로 작용 할 수 있습니다

 

  즉시로딩 지연로딩 + 캐시 X 지연 로딩 + 캐시 O
(= 프록시패턴)
최신데이터
메모리 사용량 많음 적음 많고 적음 사이 어딘가
실행시 병목점 생성시 이미지 사용하는 순간 알기 힘듬

 

4. 프록시 패턴의 응용

 

만약 사용자와의 상호작용이 매우 중요하다면 클라이언트에게 더 많은 권한을 줄 수 있게 수정함이 옳다고 판단했습니다. 

때에 따라 다음과 같이 클라이언트에게 리소스 생성 , 삭제 , 조회의 권한을 주는 것이 효과적일 수 있습니다.

 

아래 코드는 프록시 패턴과는 거리가 있고 

캡슐화를 완벽히 하고 있지 있습니다.

 

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


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

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

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

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



    public void draw(Canvas canvas, float x, float y) {
        if (image == null) {
            assert (false);
        }
        canvas.draw(this.image, x, y);
    }
    ...
    }