이 글은 책 <스프링 입문을 위한 자바 객체 지향의 원리와 이해>의 6장 스프링이 사랑한 디자인 패턴을 참고하여 작성하였습니다.
프록시란 대리자/대리인이란 뜻을 가지고 있다. 대리자는 뭔가를 대신 해주는 사람이라는 뜻인데, 그래서 프록시 패턴은 어떤 클래스의 메소드(A 메소드라 하겠다)를 호출할 때 직접 호출하는 것이 아니고, 그 메소드(A)를 대신 호출해주는 프록시 클래스의 메소드(B 메소드라 하겠다)를 호출하는 것을 말한다.
여기서 A 메소드와 B 메소드의 이름은 같다. 이런 프록시 패턴을 구현하기 위해 원래 클래스와 프록시 역할의 클래스는 같은 인터페이스를 구현하고 A, B 메소드는 그 인터페이스의 추상 메소드를 오버라이드 하여 구현한다.
우선 프록시 패턴이 쓰이지 않은 코드부터 살펴보자.
Service.java
package proxyPattern;
public class Service {
public String runSomething() {
return "서비스 짱!!";
}
}
ClientWithNoProxy.java
package proxyPattern;
public class ClientWithNoProxy {
public static void main(String[] args) {
// 프록시를 이용하지 않은 호출
Service service = new Service();
System.out.println(service.runSomething());
}
}
위에서도 언급하였지만, 프록시 패턴의 경우 실제 서비스 객체가 가진 메소드와 같은 이름의 메소드를 호출하게 되는데 이를 위해 실제 서비스 클래스와 프록시 클래스가 같은 인터페이스를 구현한다.
인터페이스를 사용하면서 서비스 객체가 들어갈 자리에 대리자 객체를 대신 투입해서 클라이언트 쪽에서는 실제 서비스 객체를 통해 메소드를 호출하고 반환값을 받는지, 프록시 객체의 메소드를 호출하고 반환값을 받는지 모르게 처리하는 것이 가능하다.
IService.java: 서비스 클래스와 프록시 클래스가 구현하는 인터페이스
package proxyPattern;
public interface IService {
String runSomething();
}
Service.java: 실제 서비스 객체
package proxyPattern;
public class Service implements IService {
@Override
public String runSomething() {
return "서비스 객체의 리턴값입니다!";
}
}
Proxy.java: 프록시 객체. 서비스 객체의 runSomething()
을 대신 호출하는 runSomthing()
을 가지고 있다.
package proxyPattern;
public class Proxy implements IService {
IService service1;
public String runSomething() {
System.out.println("호출에 대한 흐름 제어가 주목적이고, 반환 결과를 그대로 전달한다.");
service1 = new Service();
return service1.runSomething();
}
}
ClientWithProxy.java
package proxyPattern;
public class ClientWithProxy {
public static void main(String[] args) {
// 프록시를 이용한 호출
IService proxy = new Proxy();
System.out.println(proxy.runSomething()); // 프록시 객체의 메소드를 호출하고 있다.
}
}
실행 결과:
프록시 패턴은, 프록시 객체의 메소드가 기존 서비스 객체 메소드의 반환값을 그대로 리턴한다.
프록시 패턴은 반환값은 건들이지 않으면서 제어의 흐름을 변경하거나 다른 로직을 수행하기 위해 사용한다.
프록시 패턴은 스프링에서 AOP를 구현할 때 사용되는 패턴이다.
(AOP에 대한 간단한 설명은 이 글에서 보실 수 있습니다.)
프록시 패턴은 객체지향적 설계 원칙의 OCP(개방 폐쇄 원칙), DIP(의존 역전 원칙)이 적용된 패턴이다.
데코레이터는 장식하는 사람이라는 뜻일 것이다. 장식을 한다는 것은 원래 것에 무엇가를 더 덧붙여 꾸민다는 것이다. 이름처럼 데코레이터 패턴은 원래 메소드의 반환값에 어떤 것을 덧붙여 리턴하는 패턴을 의미한다.
데코레이터 패턴은 프록시 패턴과 구현하는 방법이 완전히 동일하다. 달라지는 것은 데코레이터 객체의 메소드는 원래 메소드에 반환값에 뭔가를 덧붙이거나 수정하여 클라이언트에게 전달한다는 것이다.
그래서 데코레이터 패턴을 프록시 패턴과 같은 글에 정리하였다.
아래는 데코레이터 패턴의 예시 코드이다. 서비스 클래스는 위의 프록시 패턴의 것과 같으니 생략하고, 데코레이터 클래스와 그 클래스의 메소드를 호출하는 메인 메소드가 있는 클래스의 코드만 적으려고 한다.
Decorator.java
public class Decorator implements IService {
IService service;
@Override
public String runSomething() {
System.out.println("클라이언트에게 반환값에 장식을 더해 리턴합니다.");
service = new Service();
return "이거는 장식이고요, 뒤는 " + service.runSomething();
}
}
ClientWithDecorator.java
public class ClientWithDecorator {
public static void main(String[] args) {
// 프록시를 이용한 호출
IService decorator = new Decorator();
System.out.println(decorator.runSomething());
}
}