Pattern - Proxy
나는 항상 준비되어 있지 않는 일들을 했다. 바로 그것이 성장하는 방법이라고 생각했다. 스스로 해낼 수 있다는 확신이 들지 않는 순간에도 그것을 끝까지 도전하면 당신은 돌파구를 찾을 수 있을 것이다. - 마리사 메이어, 야후 CEO
의도
다른 객체에 대한 접근을 제어하기 위한 대리자 또는 자리 채움자 역할을 하는 객체를 둡니다.
동기
-
어떤 객체에 대한 접근을 제어하는 한 가지 이유는 실제로 그 객체를 사용할 수 있을 때까지 객체 생성과 초기화에 들어가는 비용 및 시간을 물지 않겠다는 것입니다. 그래픽 객체를 문서 안에 넣을 수 있는 문서 편집기의 예를 다시 생각해 봅시다. 래스터 이미지와 같은 그래픽 객체를 생성하려면 비용이 많이 듭니다. 그러나 문서를 읽어내는 것은 이런 그래픽 객체가 있든 없든 매우 빠르게 진행되어야 합니다. 또한 문서가 읽히는 그 시점에서 모든 내용을 다 읽어올 필요는 없습니다. 이미지의 모든 내용이 한꺼번에 한 문서에 다 보일 필요는 없기 때문입니다.
-
이런 제약 사항들로 생성이나 관리가 어려운 객체라면 꼭 필요한 때에만 이 객체를 생성하도록 하는 방법이 제기되었습니다. 그러나 이미지가 찍힐 자리에 어떤 내용을 채워넣을 수 있을까요? 이 이미지가 필요할 때만 생성된다는 사실을 감추면서 어떻게 전체 편집기의 구현을 복잡하게 만들지 않을 수 있을까요? 이런 최적화가 렌더링 혹은 서식 설정 코드에 영향을 주어서도 안됩니다.
위의 동기에 대한 해결책은 실제 이미지의 대역을 맡을 이미지 프록시라는 또 다른 객체를 사용하는 것입니다. 프록시는 이미지 처럼 동작하고, 필요할 때 이미지의 인스턴스를 만들어 냅니다.
이미지 프록시는 문서 편집기가 실제로 Draw() 연산을 통해서 화면에 그리기 원할 때만 실제 이미지를 생성합니다. 프록시는 자신이 받는 메세지를 실제 이미지에 전달하고 이미지 생성후 이미지에 대한 참고를 계속 유지해야 합니다. 이미지가 다른 파일에 저장되어 있다면 실제 객체에 대한 참조자로 파일 이름을 관리하면 됩니다. 또한 프록시는 자신이 책임져야할 이미지의 넓이와 높이를 한계 정보로 관리합니다. 이는 이미지의 실제적인 인스턴스 없이도 문서가 관리해야하는 이미지의 크기에 대한 요청을 처리할 수 있게 됩니다. 즉, 문서를 읽을 때 이미지 자리에 일단 이미지 크기 만큼 정보다 있다는 정도는 알려줘야 한다는 것입니다.
활용성
- 원격지 프록시 ( Remote Proxy )
- 서로 다른 주소 공간에 존재하는 객체를 가리키는 대표 객체
- 가상 프록시 ( Virtual Proxy )
- 요청이 있을 때만 고비용 객체를 생성합니다.
- 보호용 프록시 (Protection Proxy)
- 원래 객체에 대한 실제 접근을 제어 합니다.
- 스마트 참조자 (Smart Reference)
- 실제 객체에 접근이 일어날 때 추가적인 행동을 수행합니다.
항목에 대한 설명
- Proxy Image Proxy
실제로 참조할 대상에 대한 참조자를 관리합니다. Real Subject 와 Subject 인터페이스가 동일하면 프록시는 Subject에 대한 참조자를 갖습니다. Subject와 동일한 인터페이스를 제공하여 실제 대상을 대체할 수 있어야 합니다. 실제 대상에 대한 접근을 제어하고 실제 대상의 생성과 삭제를 책임집니다.
- 원격지 프록시 : 요청 메세지와 인자를 인코딩 하여 이를 다른 주소 공간에 있는 실제 대상에게 전달합니다.
- 가상의 프록시 : 실제 대상에 대한 추가적 정보를 보유하여 실제 접근을 지연할 수 있도록 해야합니다.
-
보호용 프록시 : 요청한 대상이 실제 요청할 수 있는 권한이 있는지 확인합니다.
-
Subject Graphic RealSubject와 Proxy에 공통적인 인터페이스를 정의하여, RealSubject가 요청되는 곳에 Proxy를 사용할 수 있게 합니다.
- RealSubject Image 프록시가 대표하는 실제 객체 입니다.
결과
프록시 패턴은 어떤 객체에 접근할 때 추가적인 간접화 통로를 제공합니다. 이렇게 추가된 간접화 통로는 프록시의 종류에 따라서 여러가지 쓰임새가 있습니다.
- 원격지 프록시는 객체가 다른 주소 공간에 존재한다는 사실을 숨길 수 있습니다.
- 가상 프록시는 요구에 따라 객체를 생성하는 등 처리를 최적화 할 수 있습니다.
- 보호용 프록시 및 스마트 참조자는 객체가 접근할 때 마다 추가 관리를 책임 집니다. 객체를 생성할 것인지 삭제할 것인지를 관리합니다.
- 기록 시점 복사 : 이 최적화는 요구가 들어올 때만 객체를 생성하는 개념과 관련있는데, 덩치가 크고 복잡한 객체를 복사하려면 비용이 만만치 않습니다. 만약, 사본이 변경되지 않고 원본과 똑같다면, 굳이 이 비용을 물 필요가 없습니다. 프록시를 사용해서 복사 절차를 미룸으로써, 사본이 수정될 때만 실제 복사 비용을 물게 만드는 것입니다.
- 프록시에서 중요한 부분 중의 하나는 흐름제어만 할 뿐 결과값을 조장하거나 변경시키면 안됩니다.
코드 예제 – 기본 샘플
package DesignPattern.gof_proxy.sample01;
public class OrderMain {
public static void main(String[] args) throws Exception {
OrderExecutor orderExecutor = new OrderExecutorProxy();
orderExecutor.callOrder("커피요청");
}
}
package DesignPattern.gof_proxy.sample01;
public interface OrderExecutor {
public void callOrder(String requestName) throws Exception;
}
package DesignPattern.gof_proxy.sample01;
public class CoffeOrder implements OrderExecutor {
public void callOrder(String requestName) throws Exception {
System.out.println(requestName + " is waiting for receiving result.");
}
}
package DesignPattern.gof_proxy.sample01;
public class OrderExecutorProxy implements OrderExecutor {
private OrderExecutor orderExecutor;
public OrderExecutorProxy(){
orderExecutor = new CoffeOrder();
}
public void callOrder(String requestName) throws Exception {
System.out.println("커피를 요청 하기 위한 사전 작업 진행 !");
orderExecutor.callOrder(requestName);
System.out.println("커피를 전달 받아 추가 작업 진행 !");
}
}
코드 예제 – 추가 샘플
package DesignPattern.gof_proxy.sample02;
import java.sql.SQLException;
public class SQLCaller {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
JDBCExecutor jdbcExecutor = new JDBCExecutorProxy(new SelectExecutor());
jdbcExecutor.executeQuery("SELECT * FROM USERS;");
}
}
// STMTExecutor
package DesignPattern.gof_proxy.sample02;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
public interface STMTExecutor {
public ResultSet executeSTMT(String SQL, Connection connection) throws SQLException;
}
// SelectExecutor
package DesignPattern.gof_proxy.sample02;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class SelectExecutor implements STMTExecutor {
/**
* STMT를 실행하는 함수
* @param SQL
* @param connection
* @return
* @throws SQLException
*/
public ResultSet executeSTMT(String SQL, Connection connection) throws SQLException {
ResultSet rs;
Statement statement = connection.createStatement();
try{
rs = statement.executeQuery(SQL);
}finally {
statement.close();
}
return rs;
}
}
// JDBCExecutor
package DesignPattern.gof_proxy.sample02;
import java.sql.SQLException;
public interface JDBCExecutor {
public void executeQuery(String SQL) throws SQLException, ClassNotFoundException;
}
// JDBCExecutorProxy
package DesignPattern.gof_proxy.sample02;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JDBCExecutorProxy implements JDBCExecutor {
private static final String JDBC_DRIVER = "com.mysql.jdbc.Driver";
private static final String DB_URL = "jdbc:mysql://localhost/EMP";
private static final String USER = "username";
private static final String PASS = "password";
STMTExecutor stmtExecutor;
public JDBCExecutorProxy(STMTExecutor stmtExecutor) {
this.stmtExecutor = stmtExecutor;
}
/**
* SQL을 실행하는 Proxy 함수
* @param SQL
* @throws SQLException
*/
public void executeQuery(String SQL) throws SQLException , ClassNotFoundException{
Class.forName(JDBC_DRIVER);
Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);
ResultSet rs = stmtExecutor.executeSTMT(SQL, conn);
try{
rs.beforeFirst();
while (rs.next()) {
//Retrieve by column name
int id = rs.getInt("id");
String first = rs.getString("first");
System.out.print("ID: " + id);
System.out.print(", First: " + first);
}
}finally {
rs.close();
conn.close();
}
}
}