프록시 패턴
다른 객체를 대변하는 객체를 만들어서 주 객체에 대한 접근을 제어할 수 있다.
Subject
- Proxy를 구현하기 위해 Interface로 설계되어 있으며 RealSubject와 Proxy는 이를 구현한다.
RealSubject
- 실제 서비스를 하는 주 객체이다.
Proxy
- Client가 Subject에게 접근하면 Proxy에게 접근이 된다.
- Proxy는 접근을 제어할 수도 있고 추가 설정을 한 후 RealSubject에게 위임하여 RealSubject의 실제 서비스를 실행시킬 수 있다.
예제
Subject
public interface Subject {
void print();
}
- Subject는 주 객체와 프록시가 사용할 기능을 정의한다.
RealSubject
public class RealSubject implements Subject {
@Override
public void print() {
System.out.println("안녕하세요!");
}
}
- RealSubject는 실제 서비스들을 하는 주 객체가 된다.
- 실제 필요한 로직을 작성한다.
Proxy
public class Proxy implements Subject {
Subject realSubject;
@Override
public void print() {
realSubject = new RealSubject();
System.out.println("프록시가 제어 합니다.");
realSubject.print();
}
}
- Proxy에서는 필드에 realSubject를 가지고 있다.
- print() 메서드가 호출되면 프록시는 상황에 맞게 제어 혹은 기능을 추가할 수 있다.
- 그 후 원하는 지점에 realSubject의 print()를 호출하여 실제 서비스를 수행한다.
public class Main {
public static void main(String[] args) {
Subject proxy = new Proxy();
proxy.print();
}
}
- 클라이언트에서는 프록시로 접근하므로 접근이 제한된 것을 알 수 있다.
보호 프록시
- 프록시를 조금 더 활용하면 보호 프록시를 만들 수 있다.
- 자바에서 제공해주는 프록시를 이용하면 더욱 간단하게 만들 수 있다.
- 위의 다이어그램에서 InvocationHandler가 추가된 것을 알 수 있다.
- InvocationHandler는 자바에서 제공해주는 API를 사용하면 간단하게 구현이 가능하다.
예제
- Person이라는 객체가 있고 그 안에는 name이 들어가 있다.
- User와 NoUser가 존재한다고 할 때 User에게는 getName, setName의 권한을 준다.
- NoUser에게는 getName의 권한을 준다.
Subject
public interface Person {
String getName();
void setNmae(String name);
}
- Person은 Subject이며 하위 클래스에서 구현할 기능인 getName, setName을 정의하였다.
RealSubject
public class PersonImpl implements Person {
String name;
public PersonImpl(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public void setNmae(String name) {
this.name = name;
}
}
- Person을 구현한 RealSubject인 PersonImpl이다.
InvocationHandler
public class UserHandler implements InvocationHandler {
Person person;
public UserHandler(Person person) {
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().startsWith("set")) {
return method.invoke(person, args);
}
return null;
}
}
- InvocationHandler를 구현한 UserHandler이다.
- Person을 필드에 멤버 변수로 가지고 있다.
- User는 getName, setName이 모두 허용되므로 그에 따른 요청이 들어올 때 person에게 invoke 해준다.
public class NoUserHandler implements InvocationHandler {
Person person;
public NoUserHandler(Person person) {
this.person = person;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().startsWith("get")) {
return method.invoke(person, args);
} else if (method.getName().startsWith("set")) {
System.out.println("접근권한이 없습니다.");
}
return null;
}
}
- InvocationHandler를 구현한 NoUserHandler이다.
- UserHandler와 동일하게 Person을 가지고 있다.
- NoUser는 getName만 가능하므로 setNmae이 호출되면 권한이 없음을 알려준다.
프록시는 자바에서 제공하는 프록시를 사용할 것이므로 따로 설계하지 않는다.
Test
public class Main {
public static void main(String[] args) {
Person person = new PersonImpl("홍길동");
// 1
Person userProxy = getUserProxy(person);
System.out.println("====== UserProxy ======");
System.out.println(userProxy.getName());
userProxy.setNmae("김삿갓");
System.out.println(userProxy.getName());
// 2
Person noUserProxy = getNoUserProxy(person);
System.out.println("====== NoUserProxy ======");
System.out.println(noUserProxy.getName());
noUserProxy.setNmae("홍길동");
System.out.println(noUserProxy.getName());
}
private static Person getUserProxy(Person person) {
return (Person) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new UserHandler(person));
}
private static Person getNoUserProxy(Person person) {
return (Person) Proxy.newProxyInstance(
person.getClass().getClassLoader(),
person.getClass().getInterfaces(),
new NoUserHandler(person));
}
}
- 우선 PersonImpl을 통해 홍길동이라는 Person을 생성한다.
- User, NoUser Proxy를 자바에서 제공하는 Proxy를 통해 생성하였다.
- Proxy.newProxyInstance의 두 번째 전달 인자로 해당 클래스의 인터페이스를 넘긴다.
- 그 이유는 자바에서는 인터페이스를 구현한 객체만 프록시를 만들 수 있다.
- 그리고 세 번째 전달 인자를 보면 위에서 생성한 Handler를 넘기는 것을 알 수 있다.
- 이를 통해 프록시는 행동을 handler에게 넘기고 handler는 권한이 있다면 realSubject에게 invoke 한다.
- 1에서는 userProxy를 통해 person 이름 get 하여 출력하고 다른 이름으로 set 하였다.
- 2에서는 noUserProxy를 통해 person 이름 get 하여 출력하고 다른 이름으로 set 하였다.
- 결과를 확인해보면 정상적으로 프록시가 접근을 제어하는 것을 알 수 있다.
'디자인 패턴' 카테고리의 다른 글
스테이트 패턴(State Pattern) (0) | 2019.12.20 |
---|---|
컴포지트 패턴(Composite Pattern) (0) | 2019.12.20 |
이터레이터 패턴(Iterator Pattern) (0) | 2019.12.17 |
템플릿 메서드 패턴(Template Method Pattern) (0) | 2019.12.17 |
퍼사드 패턴(Facade Pattern), 최소 지식 원칙 (0) | 2019.12.16 |