삽질 주도 개발
article thumbnail
Published 2022. 11. 21. 18:12
GoF Design Pattern - Proxy Pattern Architecture

GoF Structural Pattern에 해당하는 Proxy Pattern에 대해서 알아보자.

 

GoF 책에서는 다음과 같이 프록시 패턴의 의도를 밝힌다.

다른 객체에 대한 접근을 제어하기 위한 대리자 또는 자리채움자 역할을 하는 객체를 둡니다.

 

구조는 다음과 같다.

 

Subject 구현체(Proxy)로 Request() 함수를 호출할 때 RealSubject를 참조하고 있는 Proxy가 RealSubject를 대리하는 구조이다.

 

 

프록시를 사용하면 다양한 역할을 수행할 수 있다. 메서드 실행 이전 이후에 대한 처리, 캐싱, lazy loading 등 다양한 역할을 한다고 한다.

 

오늘은 메서드 실행 이전 이후에 대한 로그 그리고 lazy loading에 대해서 간단한 예시를 들어 이해해보려고 한다.

 

주문을 하기 위해 상품을 담고, 결제 처리를 하는 Order 클래스가 있다고 가정한다.

 

로깅 예시 코드 - before

public class Main {
    public static void main(String[] args) {
        Order order = new Order();  // 직접 객체 접근
        order.insert("apple");
        order.process();
    }
}
public class Order {

    private final List<String> products = new ArrayList<>();

    public Order() {}

    public void insert(String product) {
        products.add(product);
    }

    public void process() {}
}

 

위와 같은 클래스에서 프록시를 이용해 메서드 실행 이전과 이후에 대한 로그를 남기려고 한다.

 

"insert 혹은 process 메서드 내에 로그를 남기는 코드를 작성하면 되는 것이 아니냐?"라고 할 수 있다.

 

하지만, Order가 DefaultOrder가 있고, RocketOrder가 있다면? 더 다양한 주문에 대한 클래스가 있다면 어떻게 될까?

 

매번 클래스에 같은 로그를 남기는 코드를 중복해서 작성해야 한다. 이때 다형성과 프록시를 사용한다면 로그 처리를 분리할 수 있다.

 

로깅 예시 코드 - after

public interface Orderable {
    void insert(String product);
    void process();
}
public class Order implements Orderable{
    private final List<String> products = new ArrayList<>();

    @Override
    public void insert(String product) {
        products.add(product);
    }

    @Override
    public void process() {}
}
public class OrderProxy implements Orderable{
    private final Orderable order;  

    public OrderProxy(Orderable order) {
        this.order = order; // interface로 실제 구현체를 주입
    }

    @Override
    public void insert(String product) {
        System.out.printf("Log: [%s]를 담았습니다.%n", product);
        order.insert(product);
    }

    @Override
    public void process() {
        long before = System.currentTimeMillis();
        order.process();
        System.out.printf("Log: process time is %d ms\n",System.currentTimeMillis() - before);
    }
}
public class Main {
    public static void main(String[] args) {
        Orderable order = new OrderProxy(new Order());
        order.insert("apple");
    }
}

 

위와 같이 실제 클래스에 불필요한 요소들은 프록시 객체에게 위임하여 비즈니스 로직만을 남겨 깔끔하게 코드를 작성할 수 있게 되었다.

 


다음은 객체 생성에 대한 비용이 클 경우에 사용하지 않는 시점에는 객체 초기화를 하지 않고, 실제로 객체 사용할 때 인스턴스를 생성하도록 하는 lazy loading을 구현하는 예시이다.

 

Lazy Loading 예시 코드

다른 클래스의 변경없이 LazyLoad를 위한 클래스를 추가했다. (간단한 예시이므로 동시성은 고려하지 않았다.)

public class Order implements Orderable{
    private final List<String> products = new ArrayList<>();

    public Order() {
        // heavy initialization
    }

    @Override
    public void insert(String product) {
        products.add(product);
    }

    @Override
    public void process() {

    }
}
public class OrderProxyLazyLoad implements Orderable{
    private Orderable order;    // 프록시 생성시 원본은 초기화하지 않음

    @Override
    public void insert(String product) {
        if(order == null) {
            createInstance();
        }
        System.out.printf("Log: [%s]를 담았습니다.%n", product);
        order.insert(product);
    }

    @Override
    public void process() {
        if(order == null) {
            createInstance();
        }
        long before = System.currentTimeMillis();
        order.process();
        System.out.printf("Log: process time is %d ms\n", System.currentTimeMillis() - before);
    }

    private void createInstance() {
        if(order != null) {
            order = new Order();
        }
    }
}
public class Main {
    public static void main(String[] args) {
        Orderable orderLazyLoad = new OrderProxyLazyLoad();
        orderLazyLoad.insert("banana");  // 실제 인스턴스 생성
    }
}

 

위와 같이 실제로 Orderable 타입으로 프록시 객체를 생성했지만, 내부적으로 원본 클래스는 인스턴스를 만들지 않고 있다.

그리고 실제로 인스턴스를 사용할 때 초기화를 해준다. 이로 인해 사용하지 않고 있지만 메모리를 차지하고 있는 리소스 낭비를 최소화할 수 있다.

 

프록시 패턴은 활용 사례가 굉장히 다양한데, 스프링을 쓴다면 분명 들어본 기억이 있을 것이다. 메커니즘을 이해한다면 어디에 쓰이는지 유추 가능하다고 본다!

 


Reference

https://johngrib.github.io/wiki/pattern/proxy/

 

프록시 패턴 (Proxy Pattern)

컴포넌트 자체가 아니라 컴포넌트의 대리자와 통신하도록 해준다

johngrib.github.io

https://ko.wikipedia.org/wiki/%ED%94%84%EB%A1%9D%EC%8B%9C_%ED%8C%A8%ED%84%B4

 

프록시 패턴 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전.

ko.wikipedia.org

 

'Architecture' 카테고리의 다른 글

GoF Design Pattern - Template Method Pattern  (0) 2022.11.15
GoF Design Pattern - Adapter Pattern  (0) 2022.11.15