[디자인 패턴] 프록시(Proxy) 패턴이란?
안녕하세요? 제이온입니다.
저번 시간에는 데코레이터 패턴에 대해 알아 보았습니다. 오늘은 프록시 패턴을 설명하겠습니다.
프록시(Proxy) 패턴
실제 기능을 수행하는 객체 대신 가상의 객체를 사용해 로직의 흐름을 제어하는 디자인 패턴입니다. 우리가 흔히 한국에서 들어가지 못하는 사이트를 뚫을 때, 프록시 서버를 이용하곤 합니다. 이렇게 중계 기능을 하는 것을 프록시라고 합니다. 프록시 패턴에서는 실제 기능을 수행하는 객체를 바로 접근해서 제어하지 않고, 가상의 객체를 통해 우회하여 실제 기능에 접근한다는 특징이 있습니다.
프록시(Proxy) 패턴 예시
예를 들어, 제품 목록을 보여 주는 GUI 프로그램이 있다고 합시다. 이 프로그램은 목록 중 일부를 화면에 보여주고, 스크롤을 할 때 나머지 목록을 화면에 표시합니다. 예를 들어, 우리가 유튜브에 들어갔을 때 한 페이지에 모든 영상이 보이는 것이 아니라 추가적인 영상은 스크롤해야 보이는 것과 유사합니다.
아무튼 제품 목록을 구성할 때 관련된 이미지를 로딩하도록 구현할 수 있는데, 이 경우 불필요하게 메모리를 사용하는 문제가 생깁니다. 예를 들어, 현재 화면에서 보이지는 않지만 스크롤하면 더 많은 제품 이미지를 볼 수 있다고 합시다. 그런데, 여기서 바로 모든 관련된 이미지를 로딩하면, 아직 보이지 않는 이미지에 대해서도 로딩을 해야하므로 대기 시간이 길어질 수 있습니다.
불필요한 이미지 로딩에 따른 메모리 낭비와 이미지 로딩에 따른 화면 출력 대기 시간이 길어지는 문제를 해결하는 방법은 이미지가 실제 화면에 보여질 때 이미지 데이터를 로딩하는 것입니다. 즉, 이미지가 필요할 때만 이미지 데이터를 로딩하는 것이죠. 아래와 같은 구조로 설계할 수 있습니다.
ListUI 입장에서 이미지를 바로 로딩할 때는 Image 클래스를 사용하고, 동적으로 로딩할 때는 Image를 이용해서 만든 DynamicLoadingImage 클래스를 사용하면 됩니다. 다만, 이렇게 하면 이미지 로딩 방식을 변경해야할 때 ListUI 코드를 변경하는 문제가 생깁니다.
가령, 화면에 보여줄 목록의 개수가 4개 미만일 때는 Image 클래스를 통해 바로 로딩하고, 그렇지 않다면 DynamicLoadingImage 클래스를 통해 로딩한다고 합시다. 그렇다면, ListUI 안에는 Image와 DynamicLoadingImage를 구분하는 조건문이 생길 것이므로 OCP를 위반하게 됩니다.
이런 상황에서 ListUI 변경 없이 이미지 로딩 방식을 교체할 수 있도록 해 주는 패턴이 프록시 패턴입니다. 구조는 다음과 같습니다.
ListUI는 Image라는 상위 클래스에 의존합니다. 여기서 RealImage는 실제 이미지 데이터를 로딩하는 구현 클래스이며, ProxyImage가 프록시 패턴에서 프록시 역할을 합니다.
public final class ProxyImage implements Image {
private String name;
private RealImage image;
public ProxyImage(final String name) {
this.name = name;
}
@Override
public void draw() {
if (image == null) {
image = new RealImage(name);
}
System.out.println("프록시를 이용합니다.");
image.draw();
}
}
ProxyImage는 처음부터 부러 RealImage 객체를 생성하지 않고, 최초로 draw() 메소드가 호출되었을 때 생성합니다. 즉, 특정 이용자가 스크롤을 내린 그 시점에서 이미지 데이터를 로딩하는 RealImage에게 메시지를 보내는 것입니다.
public final class RealImage implements Image {
private final String name;
public RealImage(final String name) {
this.name = name;
}
@Override
public void draw() {
System.out.println("이미지를 로딩합니다.");
}
}
이 객체는 실제 이미지 데이터를 로딩하는 역할을 담당합니다.
public final class ListUI {
private List<Image> images;
public ListUI(List<Image> images) {
this.images = images;
}
public void onScroll(final int start, final int end) {
for (int i = start; i <= end; i++) {
final Image image = images.get(i);
image.draw();
}
}
}
ListUI는 Image 타입을 사용하기 때문에 실제 타입이 RealImage인지 ProxyImage인지 여부는 모릅니다. 단지, Image 타입의 draw() 메소드를 이용해서 이미지를 그려 달라고 할 뿐입니다. ListUI는 이미지 데이터 리스트를 갖고 있고, 스크롤을 내렸을 때 특정 이미지를 로딩하여 그려주는 책임을 지니고 있습니다.
이제, 실제로 이미지를 로딩해 봅시다. 아래와 같이 6개의 이미지가 있는데, 상위 4개는 바로 이미지를 로딩하고 나머지는 화면에 보여지는 순간에 로딩하도록 구현한다고 합시다.
public final class Application {
public static void main(String[] args) {
final List<String> names = Arrays.asList("yeeSan1", "yeeSan2", "yeeSan3", "yeeSan4", "yeeSan5", "yeeSan6");
final List<Image> images = new ArrayList<>(names.size());
for (int i = 0; i < names.size(); i++) {
if (i <= 3) {
images.add(new RealImage(names.get(i)));
continue;
}
images.add(new ProxyImage(names.get(i)));
}
final ListUI listUI = new ListUI(images);
listUI.onScroll(0, 3);
System.out.println();
listUI.onScroll(4, 5);
}
}
위 코드를 실행해 보겠습니다. 상위 4개에 대해서는 바로 이미지가 로딩되었다는 메시지가 출력되어야 하고, 나머지는 프록시를 이용한다는 메시지와 이미지가 로딩되었다는 메시지가 같이 출력되어야 합니다.
잘 나오는 군요. 이렇게 우리는 처음부터 모든 이미지를 로딩하는 것이 아니라, 특정 상황일 때만 이미지를 로딩하도록 구현하였습니다.
프록시(Proxy) 패턴 vs 데코레이터(Decorator) 패턴
프록시 패턴과 데코레이터 패턴의 구조를 보니까 둘은 상당히 비슷한 것을 알 수 있습니다. 하지만, 의도에서 분명한 차이가 있습니다.
프록시 패턴은 실제 객체에 대한 접근을 제어하는데 초점이 맞춰져 있는 반면에 데코레이터 패턴은 기존 객체의 기능을 확장하는데 초점을 맞추고 있습니다. 따라서 '접근 제어'냐 '기능 확장'이냐 따라 잘 구분하여 사용하시면 되겠습니다.
정리
지금까지 프록시 패턴을 알아 보았습니다. 특정 객체를 바로 접근할 때 구조 또는 유지 보수 측면에서 바람직하지 않은 문제가 발생한다면 프록시 패턴을 한 번 고안해 보는 것을 추천드립니다.
출처
개발자가 반드시 정복해야 할 객체 지향과 디자인 패턴 - 최범균
'개발 이야기 > 디자인 패턴' 카테고리의 다른 글
[디자인 패턴] 데코레이터(Decorater) 패턴이란? (0) | 2021.03.15 |
---|---|
[디자인 패턴] 상태(State) 패턴이란? (4) | 2021.03.12 |
[디자인 패턴] 템플릿 메소드(Template Method) 패턴이란? (0) | 2021.03.11 |
[디자인 패턴] 전략(Strategy) 패턴이란? (3) | 2021.03.10 |
댓글