프록시 패턴의 정의
프록시 패턴(Proxy Pattern)은 특정 객체로의 접근을 제어하는 대리인(특정 객체를 대변하는 객체)을 제공한다.
프록시 패턴 UML
Proxy와 RealSubject는 모두 Subject 인터페이스를 구현하며, 따라서 어떤 클라이언트에서라도 Proxy와 Subject를 똑같은 방식으로 사용할 수 있다.
Proxy는 RealSubject 객체의 레퍼런스를 가지고 있으면서, RealSubject로의 접근을 제어한다. 진짜 작업을 처리해야 할 때는 RealSubject 객체 레퍼런스에게 요청 request()을 전달한다.
프록시 패턴 예제 코드
public interface Icon {
public int getIconWidth();
public int getIconHeight();
public void paintIcon(Component c, Graphics g, int x, int y);
}
public class ImageProxy implements Icon {
volatile ImageIcon imageIcon;
final URL imageURL;
Thread retrievalThread;
boolean retrieving = false;
public ImageProxy(URL imageURL) {
this.imageURL = imageURL;
}
@Override
public int getIconWidth() {
if (imageIcon != null) {
return imageIcon.getIconWidth();
}
else {
return 800;
}
}
@Override
public int getIconHeight() {
if (imageIcon != null) {
return imageIcon.getIconHeight();
}
else {
return 600;
}
}
synchronized void setImageIcon(ImageIcon imageIcon) {
this.imageIcon = imageIcon;
}
@Override
public void paintIcon(final Component c, Graphics g, int x, int y) {
if (imageIcon != null) {
imageIcon.paintIcon(c, g, x, y);
} else {
g.drawString("앨범 커서 로딩 중, 잠시만 기다려주세요...", x + 300, y + 190);
if(!retrieving) {
retrieving = true;
retrievalThread = new Thread(() -> {
try {
setImageIcon(new ImageIcon(imageURL, "앨범 커버"));
c.repaint();
} catch (Exception e) {
e.printStackTrace();
}
});
retrievalThread.start();
}
}
}
}
Icon 인터페이스는 Subject 인터페이스이며, 이를 ImageProxy 구상 클래스가 구현하고 있다.
public class ImageComponent extends JComponent {
private Icon icon;
public ImageComponent(Icon icon) {
this.icon = icon;
}
public void setIcon(Icon icon) {
this.icon = icon;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int w = icon.getIconWidth();
int h = icon.getIconHeight();
int x = (800 - w) / 2;
int y = (600 - h) / 2;
icon.paintIcon(this, g, x, y);
}
}
public class ImageProxyTestDrive {
ImageComponent imageComponent;
JFrame frame = new JFrame("앨범 커버 뷰어");
JMenuBar menuBar;
JMenu menu;
Hashtable<String, String> albums = new Hashtable<>();
public static void main(String[] args) throws Exception {
ImageProxyTestDrive testDrive = new ImageProxyTestDrive();
}
public ImageProxyTestDrive() throws Exception {
albums.put("Love Dive", "https://i.scdn.co/image/ab67616d0000b2739016f58cc49e6473e1207093");
albums.put("Fearless", "https://cdns-images.dzcdn.net/images/cover/5d27bb682fabe51b4245ec2ac4c8ff64/500x500.jpg");
albums.put("Mozart Piano Concerto No. 20", "https://i.scdn.co/image/ab67616d0000b273412d95241fe57790ba434dac");
albums.put("Chopin Piano Concerto No. 1", "https://m.media-amazon.com/images/I/71CM1GPERaL._AC_SX342_.jpg");
URL initialURL = new URL(albums.get("Love Dive"));
menuBar = new JMenuBar();
menu = new JMenu("즐겨찾는 앨범");
menuBar.add(menu);
frame.setJMenuBar(menuBar);
for(Enumeration<String> e = albums.keys(); e.hasMoreElements();) {
String name = e.nextElement();
JMenuItem menuItem = new JMenuItem(name);
menu.add(menuItem);
menuItem.addActionListener(event -> {
imageComponent.setIcon(new ImageProxy(getAlbumUrl(event.getActionCommand())));
frame.repaint();
});
}
// 프레임 및 메뉴 설정
Icon icon = new ImageProxy(initialURL);
imageComponent = new ImageComponent(icon);
frame.getContentPane().add(imageComponent);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800,600);
frame.setVisible(true);
}
URL getAlbumUrl(String name) {
try {
return new URL(albums.get(name));
} catch (MalformedURLException e) {
e.printStackTrace();
return null;
}
}
}
ImageComponent 클래스는 Icon 객체의 레퍼런스를 가지면서, 이미지를 보여주는 역할을 수행한다.
ImageProxyTestDrive에서 ImageComponent 객체는 생성되면서 ImageProxy 객체를 인자를 받는데, 이 때 paintComponent() -> paintIcon()을 통해 이미지를 불러오면 ImageProxy 객체는 ImageIcon의 이미지가 로딩되기 전까지 대신 보여지며 로딩중이라는 텍스트를 화면에 그린다. 이후 이미지 로딩이 끝나면 ImageIcon를 실제 이미지가 담긴 ImageIcon로 교체하며, 이미지가 존재하는 ImageIcon으로 화면을 다시 그린다.
프록시에서 접근을 제어하는 방법
- 원격 프록시(remote proxy) : 원격 객체의 대리인. 프록시의 메소드가 호출되면 해당 호출은 네트워크를 통해 원격 객체의 메소드를 호출시키고, 반환값은 다시 네트워크를 통해 다시 프록시에게 도달하여, 이를 프록시가 클라이언트에게 전달함.
- 가상 프록시(virtual proxy) : 생성하는 데에 많은 비용이 드는 객체의 대리인. 진짜 객체가 필요한 상황이 오기 전이나 진짜 객체가 생성 중일 때, 객체를 대신함. 객체 생성이 끝나면 그 떄부터 요청을 전달함.
- 보호 프록시(protection proxy) : 접근 권한이 필요한 자원의 대리인으로써, 해당 자원의 접근을 제어함.
동적 프록시
동적 프록시(Dynamic Proxy)는 실행 중에 프록시가 생성되는 프록시이다.
동적 프록시는 Java Reflection을 활용하여 생성할 수 있으며, Proxy 클래스는 Reflection에서 만들기 때문에 Proxy 객체의 모든 메소드 호출을 전달받는 InvocationHandler을 작성하여 이를 프록시에게 제공해야 한다. InvocationHandler는 프록시에 호출되는 모든 메소드에 응답하며 실제 요청을 RealSubject에게 보낸다.
프록시 패턴의 다양한 변종
- 방화벽 프록시(Firewall Proxy) : 일련의 네트워크 자원으로의 접근을 제어함으로써 주제를 ‘나쁜’ 클라이언트로부터 보호해 줌. (ex: 기업용 방화벽 시스템)
- 스마트 레퍼런스 프로시(Smart Reference Proxy) : 주제가 참조될 때마다 추가 행동을 제공함. (ex: C++ 스마트 포인터)
- 캐싱 프록시(Caching Proxy) : 비용이 많이 드는 작업의 결과를 임시로 저장해 줌. 여러 클라이언트에서 결과를 공유하게 해 줌으로써 계산 시간과 네트워클 지연을 줄여주는 효과도 있음. (ex: 웹 서버 프록시 및 CDN)
- 동기화 프록시(Synchronization Proxy) : 여러 스레드에서 주제에 접근할 때 안전하게 작업을 처리할 수 있게 해 줌. (ex: 분산 환경에서의 자바 스페이스)
- 복잡도 숨김 프록시(Complexity Hiding Proxy) : 복잡한 클래스의 집합으로의 접근을 제어하고, 그 복잡도를 숨겨 줌. 퍼사드 프록시(Facade Proxy)라고 부르기도 함. (ex: API Gateway?)
- 지연 복사 프록시(Copy-On-Write Proxy) : 클라이언트에서 필요로 할 때까지 객체가 복사되는 것을 지연시킴으로써 객체의 복사를 제어함. 변형된 가상 프록시 (ex; 자바의 CopyOnWriteArrayList)
핵심 정리
- 프록시 패턴을 사용하면 어떤 객체의 대리인을 내세워서 클라이언트의 접근을 제어할 수 있음. 접근을 관리하는 방법에는 여러 가지가 있음.
- 원격 프록시는 클라이언트와 원격 객체 사이의 데이터 전달을 관리해 줌.
- 가상 프록시는 인스턴스를 만드는 데 많은 비용이 드는 객체로의 접근을 제어함.
- 보호 프록시는 호출하는 쪽의 권한에 따라서 객체에 있는 메소드로의 접근을 제어함.
- 그 외에도 캐싱 프록시, 동기화 프록시, 방화벽 프록시, 지연 복사 프록시와 같이 다양한 변형된 프록시 패턴이 있음.
- 프록시 패턴의 구조는 데코레이터 패턴의 구조와 비슷하지만 그 용도는 다름.
- 데코레이터 패턴은 객체에 행동을 추가하지만 프록시 패턴은 접근을 제어함.
- 자바에 내장된 프록시 지원 기능을 사용하면 동적 프록시 클래스를 만들어서 원하는 핸들러에서 호출을 처리하도록 할 수 있음.
- 다른 래퍼(Wrapper)를 쓸 떄와 마찬가지로 프록시를 쓰면 디자인에 포함되는 클래스와 객체의 수가 늘어남
GitHub Code Reference: https://github.com/Krapi0314/Design_Patterns
GitHub - Krapi0314/Design_Patterns: Java Implementation of GoF's Design Patterns
Java Implementation of GoF's Design Patterns. Contribute to Krapi0314/Design_Patterns development by creating an account on GitHub.
github.com
Reference: http://www.yes24.com/Product/Goods/108192370
헤드 퍼스트 디자인 패턴 - YES24
유지관리가 편리한 객체지향 소프트웨어 만들기!“『헤드 퍼스트 디자인 패턴(개정판)』 한 권이면 충분하다.이유 1. 흥미로운 이야기와 재치 넘치는 구성이 담긴 〈헤드 퍼스트〉 시리즈! 하나
www.yes24.com
'객체지향 및 기반 언어 > 디자인 패턴' 카테고리의 다른 글
[Design Pattern] Chapter 13 실전 디자인 패턴 (0) | 2022.11.05 |
---|---|
[Design Pattern] Chapter 12 복합 패턴 (0) | 2022.10.30 |
[Design Patterns] Chapter 10 상태 패턴 (0) | 2022.10.23 |
[Design Patterns] Chapter 09 반복자 패턴과 컴포지트 패턴 (0) | 2022.10.23 |
[Design Patterns] Chapter 08 템플릿 메소드 패턴 (0) | 2022.10.09 |