目次

the Definition of Proxy Pattern

원격 프록시(Remote Proxy)는 일반적인 프록시 패턴(Proxy Pattern)을 구현하는 방법 가운데 하나입니다. 이 외에도 몇 가지 변형된 방법이 있는데, 잠시 후에 알아보도록 하겠습니다. 일단 지금은 일반 프록시 패턴 에 대해서 살펴보도록 하겠습니다.

프록시 패턴은 다음과 같이 정의됩니다.

프록시 패턴 - 어떤 객체에 대한 접근을 제어하기 위한 용도로
대리인이나 대변인에 해당하는 객체를 제공하는 패턴

프록시는 다른 객체에 대한 대변자라고 봐도 무방합니다. 하지만 접근을 제어하는 프록시는 어떤 것일까요? 조금 이상하게 들릴수도 있겠지만, 너무 어렵게 생각하진 않으셔도 됩니다. 뽑기 기계의 경우에는 프록시가 원격 객체에 대한 접근을 제어하고 있다고 생각하면 되거든요. 클라이언트인 모니터링용 객체에서 원격 객체하고 직접 데이터를 주고 받을 수 없었기 때문에 프록시에서 접근을 제어해 줘야 했습니다. 따라서 어떤 면에서 보면 원격 프록시가 접근을 제어해서 네트워크 관련 사항을 챙겨 줬다고 할 수도 있는 거죠. 프록시 패턴에는 수많은 변종이 있습니다. 그리고 그러한 변종들은 대개 프록시에서 접근을 제어하는 방법면에서 차이를 보입니다. 자세한 내용은 나중에 알 아보기로 하고, 일단 프록시에서 접근을 제어하는 몇가지 방법을 나열해 보면 다음과 같습니다.

프록시 패턴의 정의에 대해서는 이 정도면 된 것 같으니까 클래스 다이어그램을 살펴볼까요?

Class Diagram

Proxy Pattern

다이어그램을 훑어볼까요?

우선 RealSubject와 Proxy의 인터페이스를 제공하는 Subject 인터페이스가 있습니다. 두 객체에서 똑같은 인터페이스를 구현하기 때문에 RealSubject가 들어가야 할 자리에 Proxy를 대신 집어넣을 수 있습니다.

실제 작업은 RealSubject 객체에서 처리됩니다. Proxy는 이 객체의 대변인 역할을 하면서 이 객체에 대한 접근을 제어하죠.

Proxy에는 RealSubject에 대한 레퍼런스가 들어있습니다. Proxy에서 RealSubject를 생성하거나 제거하는 역할을 책임지는 경우도 있습니다.클라이언트는 항상 Proxy를 통해서 RealSubject하고 데이터를 주고 받습니다. ProxyRealSubject는 똑같은 인터페이스(Subject)를 구현하기 때문에 RealSubject 객체가 들어갈 자리라면 어디든지 Proxy를 대신 집어넣을 수 있습니다. ProxyRealSubject에 대한 접근을 제어하는 역할도 맡게 됩니다. RealSubject가 원격 시스템에서 돌아가거나, 그 객체를 생성하는 데 비용이 많이 들거나, 어떤 방식으로든지 RealSubject에 대한 접근이 통제되어 있는 경우에 접근을 제어하는 객체가 필요할 수 있습니다.

지금까지 일반적인 프록시 패턴에 대해서 살펴봤습니다. 이제 프록시를 활용하는 다른 방법을 살펴보도록 하죠.

Virtual Proxy

방금 전에 프록시 패턴의 정의에 대해서 배웠습니다. 그리고 그 전에 살펴본 예제는 프록시 패턴 중이 하나인 원격 프록시(Remote Proxy)를 사용하는 예제였죠. 이제 가상 프록시(Virtual Proxy)라는 다른 형식의 프록시에 대해 살펴보겠습니다. 앞으로 계속해서 느낄 수 있겠지만, 프록시 패턴은 매우 다양한 형태로 쓰입니다. 하지만 어떤 형태로 사용하든 결국은 기본 프록시 디자인을 따르게 되죠. 어떻게 그렇게 다양한 형태로 활용될 수 있는 걸까요? 그 이유는 프록시 패턴은 여러 가지 다양한 상황에 적용될 수 있기 때문입니다.

그럼 이제 가상 프록시의 예제를 살펴보도록 할까요?

CD Cover Viewer

CD 커버를 보여주는 뷰어를 만들기로 했다고 가정해 봅시다. CD 타이틀 메뉴를 만든 다음 이미지를 아마존 닷 컴 같은 온라인 서비스로부터 가져오면 꽤 편리할 것입니다. 스윙을 사용한다면 아이콘을 만든 다음, 그 아이콘 객체 로 하여금 네트워크를 통해서 이미지를 불러오도록 할 수 있을 것입니다. 그런데, 이런 방법을 사용하면 네트워크의 상태와 인터넷 속도에 따라서 CD 커버 이미지를 가져오는데 약간 시간이 걸릴 수 있기 때문에, 이미지를 불러오는 동안 화면에 뭔가 다른 걸 보여주는 게 좋습니다. 그리고 이미지를 기다리는 동안 애플리케이션 전체가 작동을 멈추 는 것도 바람직하지 않죠.

가상 프록시를 사용하면 이런 문제를 간단하게 해결할 수 있습니다. 가상 프록시가 아이콘 대신 백그라운드에서 이미지를 불러오는 작업을 처리하고, 이미지를 완전히 가져오기 전까지는 “Loading CD cover, please wait…” 같은 메세지를 보여주면 되니까요. 일단 이미지 로딩이 끝나고 나면 프록시에서는 아이콘 객체한테 모든 작업을 맡기면 됩니다.

Design of CD Cover viewer

CD 커버 뷰어 코드를 작성하기 전에 클래스 다이어그램을 살펴봅시다. 전에 만들었던 원격 프록시 클래스 다이어그램하고 똑같이 생겼지만, 여기에서는 프록시가 네트워크를 통해 연결되어 있는 다른 위치에 있는 객체를 대신하는 용도로 쓰인 것이 아니라 생성하는 데 많은 비용이 필요한 객체(아이콘용 데이터를 네트워크를 통해서 가져와야 하기 때문에 시간이 많이 걸릴 수 있죠)를 숨기기 위한 용도로 쓰입니다.

Virtual Proxy

ImageProxy 작동 방법

  1. ImageProxy에서는 우선 ImageIcon을 생성하고 네트워크 URL로부터 이미지를 불러옵니다.
  2. 이미지를 가져오는 동안에는 “Loading CD Cover, please wait…“라는 메세지를 화면에 표시합니다.
  3. 이미지 로딩이 끝나면 paintIcon(), getWidth(), getHeight()를 비롯한 모든 메소드 호출을 이미지 아이콘 객체한테 넘깁니다.
  4. 사용자가 새로운 이미지를 요청하면 프록시를 새로 만들고 위의 과정을 새로 진행합니다.

ImageProxy Class

/**
 * 네트워크를 통해서 이미지를 가져오는 동안 대신 실제객체를 대신하는 Proxy클래스입니다.<br/>
 * imageIcon의 인스턴스가 만들어진 후에 화면을 표시하게 되면 paintIcon()메소드가 호출되면서 로딩중이라는
 * 메시지가 아닌 실제 이미지가 화면에 표시됩니다.
 * 
 * @author Administrator
 *
 */
public class ImageProxy implements Icon {
	ImageIcon imageIcon;
	URL imageURL;
	Thread retrievalThread; //이미지를 가져오기위한 스레드, 사용자인터페이스가 죽지않도록 별도의 스레드를 생성.
	boolean retrieving = false;
 
 
	public ImageProxy(URL imageURL) {
		super();
		this.imageURL = imageURL;
	}
 
	/* (non-Javadoc)
	 * @see javax.swing.Icon#getIconHeight()
	 */
	public int getIconHeight() {
		if (imageIcon != null)	return imageIcon.getIconHeight();
		else return 600;
	}
 
	/* (non-Javadoc)
	 * @see javax.swing.Icon#getIconWidth()
	 */
	public int getIconWidth() {
		if (imageIcon != null)	return imageIcon.getIconWidth();
		else return 800;
	}
 
	/* (non-Javadoc)
	 * @see javax.swing.Icon#paintIcon(java.awt.Component, java.awt.Graphics, int, int)
	 */
	public void paintIcon(final Component c, Graphics g, int x, int y) {
		if (imageIcon != null) {
			//아이콘이 이미 준비되어 있으면 그 아이콘 객체의 메소드를 호출합니다.
			imageIcon.paintIcon(c, g, x, y);
		} else {
			g.drawString("Loading CD cover, please wait...", x + 300, y + 190);
			//이미지를 가져오고 있는 중이 아니면...
			if (!retrieving) {
				//이미지 로딩작업 시작.(repaint()메소드는 한 스레드에서만 호출하기 때문에, 스레드 안전성은 확보되었다고
				//할 수 있습니다.
				retrieving = true;
 
				//사용자 인터페이스가 죽지 않도록 별도의 스레드에서 이미지를 가져옵니다.
				retrievalThread = new Thread(new Runnable(){
					public void run(){
						try {
							//이 스레드 내에서 Icon객체의 인스턴스를 생성했습니다. 이미지가 완전히 로딩되어야 생성자에게 객체를 리턴합니다.
							imageIcon = new ImageIcon(imageURL, "CD Cover");
							//이미지가 확보되고 나면 repaint()메소드를 호출해서 화면을 갱신합니다.
							c.repaint();
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				});
 
				retrievalThread.start();
			}
		}
 
	}
 
}

ImageComponent Class

/**
 * @author Administrator
 *
 */
public class ImageComponent extends JComponent {
	private Icon icon;
 
	public ImageComponent(Icon icon) {
		super();
		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);
	}
 
 
}

Test of CD Cover Viewer

드디어 새로 배운 가상 프록시를 테스트해 볼 때가 되었습니다. 윈도우를 만들고 프레임을 준비하고 메뉴를 설치하고 프록시를 생성해주는 ImageProxyTestDrive클래스를 미리 준비해 뒀습니다. 테스트용 클래스 코드는 다음과 같습니다.

public class ImageProxyTestDrive {
	ImageComponent imageComponent;
	JFrame frame = new JFrame("CD Cover View");
	JMenuBar menuBar;
	JMenu menu;
	Hashtable cds = new Hashtable();
	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception{
		ImageProxyTestDrive testDrive = new ImageProxyTestDrive();
 
	}
 
	public ImageProxyTestDrive() throws Exception{
		cds.put("Ambient: Music for Airports", "http://images.amazon.com/images/P/B000003S2K.01.LZZZZZZZ.jpg");
		cds.put("Buddha Bar", "http://images.amazon.com/images/P/B00009XBYK.01.LZZZZZZZ.jpg");
		cds.put("Ima", "http://images.amazon.com/images/P/B000005IRM.01.LZZZZZZZ.jpg");
		cds.put("Karma", "http://images.amazon.com/images/P/B000005DCB.01.LZZZZZZZ.jpg");
		cds.put("MCMXC A.D.", "http://images.amazon.com/images/P/B000002URV.01.LZZZZZZZ.jpg");
		cds.put("Northern Exposure", "http://images.amazon.com/images/P/B000003SEN.01.LZZZZZZZ.jpg");
		cds.put("Selected Ambient Works, Vol. 2", "http://images.amazon.com/images/P/B000002MNZ.01.LZZZZZZZ.jpg");
		cds.put("oliver", "http://www.cs.yale.edu/homes/freeman-elisabeth/2004/9/Oliver_sm.jpg");
 
		URL initialURL = new URL((String)cds.get("Selected Ambient Works, Vol. 2"));
		menuBar = new JMenuBar();
		menu = new JMenu("Favorite CDs");
		menuBar.add(menu);
		frame.setJMenuBar(menuBar);
 
		for (Enumeration e = cds.keys(); e.hasMoreElements();) {
			String name = (String)e.nextElement();
			JMenuItem menuItem = new JMenuItem(name);
			menu.add(menuItem);
			menuItem.addActionListener(new ActionListener(){
				public void actionPerformed(ActionEvent event){
					imageComponent.setIcon(new ImageProxy(getCDUrl(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 getCDUrl(String name){
		try {
			return new URL((String)cds.get(name));
		} catch (MalformedURLException e) {
			e.printStackTrace();
			return null;
		}
	}
}

테스트 해 볼 것…

  1. 메뉴를 이용하여 다른 CD 커버를 불러옵니다. 이미지 로딩이 완료될 때까지 프록시에서 로딩중이라는 메세지를 보여주는 것을 확인해 봐야 되겠죠?
  2. 로딩중이라는 메시지가 화면에 표시된 상태에서 윈도우 크기를 조절해 봅시다. 프록시에서 이미지를 로딩하고 있을 때도 스윙 윈도우가 멈추거나 하진 않는지 확인해 봅시다.
  3. ImageProxyTestDrive에 여러분이 가지고 있는 CD를 추가해 봅시다.

다음은 로딩중일 때의 화면입니다.

under loding

로딩이 완료되면 이런 윈도우가 만들어집니다.

oliver

reference