도서 요약 / / 2023. 2. 21. 18:52

디자인 패턴 - 프록시(proxy) 패턴

udemy 강좌(Java Design Patterns & SOLID Design Principles)를 정리한 내용이다.

https://www.udemy.com/course/design-patterns-in-java-concepts-hands-on-projects/learn/lecture/9604610?start=0#overview


프록시는 무엇인가?

  • 다른 객체에 대한 대리인을 제공할 필요가 있다.

  • 프록시는 그 객체를 대신하여 동작하고 여러가지 이유로 사용되며 주요 이유는 다음과 같다.

    • 보호 프록시: 원래 객체 동작에 접근을 제어
    • 원격 프록시: 원격 객체에 대한 로컬의 사용법을 나타냄
    • 가상 프록시: 필요할 때까지 원래 객체 생성을 지연
  • 클라이언트는 프록시의 존재를 모른다. 프록시는 투명하게 동작한다.

  • 하이버네이트의 느린 로딩(Lazy Loading), AOP의 메소드 수준 보안, RMI/Web Service 스텁이 예이다.

프록시 구현

  • 프록시를 구현하면서 시작
    • 프록시는 실제 Subject와 동일한 인터페이스를 구현해야 한다.
    • 필요할 때 실제 객체를 나중에 만들거나 생성자에서 만들 수도 있다.
    • 프록시의 메소드 구현부에서 실제 객체를 위임하기 전에 프록시 기능을 구현한다.
  • 클라이언트에게 프록시 인스턴스를 제공하는 방법은 애플리케이션에서 결정된다. 팩토리를 제공할 수도 있고 클라이언트 코드에서 프록시 인스턴스를 구성하게 할 수도 있다.
  • 위에 언급된 내용은 정적 프록시이다. 자바는 또한 동적 프록시를 제공한다.

예제: UML

구현하려고 하는 것은 가상(virtual) 프록시이다. 실제 객체를 나타내는 객체를 만들 것이고 가능한 실제 객체 생성을 지연시킬 것이다. 구현부에 있는 관여되어 있는 것들을 한번 보자.

우선, Image 인터페이스가 있고 클라이언트에서 사용된다. 이 인터페이스는 구현되어야 하며 BitmapImage가 있고 실제 객체, 실제 데이터를 가지고 있다. 그리고 ImageProxy가 있고 이것이 프록시 클래스이다. 이것도 Image 인터페이스를 구현할 것이고 실제 객체에 참조를 하고 있다.

그리고 나서 클라이언트에서 사용되는 ImageFactory를 제공할 것이고 이 팩토리는 프록시 인스턴스를 전송할 것이다. 그래서 클라이언트는 프록시 객체를 사용하고 있는 것을 모르고 오직 Image 인터페이스만을 사용할 것이다.

실제 객체가 필요할 때, 예를 들어 클라이언트가 render 메소드를 호출하다면 Image의 일부인 실제 데이터가 필요하다. 디스크에서 로딩된 실제 객체를 만들 것이고 rendering을 수행할 것이다.

Java 구현

public interface Image {

    void setLocation(Point2D point2D);

    Point2D getLocation();

    void render();
}

몇 개의 인터페이스와 구체 클래스를 작성했고 프록시 디자인 패턴을 구현할 때 사용할 것이다. 시작하면, 여기 Image 인터페이스가 있다. 이 인터페이스는 클라이언트에서 사용되고 클라이언트 애플리케이션은 캔버스에 이미지 객체 인스턴스를 render할 수 있다.

public class BitmapImage implements Image {

    private Point2D location;

    private String name;

    public BitmapImage(String filename) {
        System.out.println("Loaded from disk: " + filename);
        this.name = filename;
    }

    @Override
    public void setLocation(Point2D point2D) {
        this.location = point2D;
    }

    @Override
    public Point2D getLocation() {
        return this.location;
    }

    @Override
    public void render() {
        System.out.println("Rendered " + this.name);
    }
}

그리고 BitmapImage 클래스가 있고 디스크에서 주기적으로 파일을 로드할 것이다. 이 파일은 filename을 생성자로 갖고 있다. 이 클래스의 객체를 만들면 이 클래스는 디스크에서 파일을 로드하고 이미지 데이터를 로딩할 것이다. 필요할 때까지 객체 생성을 지연하고 싶다. 생성자와는 별개로 생성될 이미지의 캔버스 위치를 나타내는 location 속성이 있다.

public class ImageProxy implements Image {

    private String name;

    private BitmapImage image;

    private Point2D location;

    public ImageProxy(String name) {
        this.name = name;
    }

    @Override
    public void setLocation(Point2D point2D) {
        if (image != null) {
            image.setLocation(point2D);
        } else {
            location = point2D;
        }
    }

    @Override
    public Point2D getLocation() {
        if (image != null) {
            return image.getLocation();
        }
        return location;
    }

    @Override
    public void render() {
        if (image == null) {
            image = new BitmapImage(name);
            if (location != null) {
                image.setLocation(location);
            }
        }

        image.render();
    }
}

그리고 ImageProxy가 있다.

public class ImageFactory {

    public static Image getImage(String name) {
        return new ImageProxy(name);
    }
}

이 Proxy를 사용하기 위해 ImageFactory가 필요할 것 같다. 왜냐하면 클라이언트는 이미지 프록시의 존재를 모르기 때문이다. 이것은 SimpleFactory이다.

public class Client {

    public static void main(String[] args) {
        Image img = ImageFactory.getImage("Ai.bmp");

        img.setLocation(new Point2D(10, 10));
        System.out.println("Image location : " + img.getLocation());
        System.out.println("rendering image now.....");
        img.render();
    }
}

클라이언트에서 해야할 일은 ImageFactory를 사용하기 위해 이미지 객체를 사용하는 것이다.

구현 고려사항

  • 프록시가 실제 객체를 어떻게 만드는지는 그 목적에 따라 다르다. 프록시의 사용 유형 중 생성의 경우는 프록시가 클라이언트 요청을 처리할 수 없을 때만 실제 객체가 생성된다. 인증 프록시는 미리 생성된 객체를 사용하고 프록시를 구성하는 동안 객체가 사용된다.
  • 프록시 그 자체는 필요할 때 생성되는 실제 객체를 대신하여 특정 상태를 유지할 수 있다.
  • 프록시에 추가된 동기화 이슈 뿐만 아니라 프록시 성능 비용에 신경써야 한다.

디자인 고려사항

  • 프록시는 일반적으로 실제 객체의 구현부에 대해 알 필요가 없다.
  • 자바로 런타임에 객체의 프록시를 만들 수 있는 동적 프록시를 사용할 수 있다.
  • 프록시는 보안을 구현하거나 로딩을 지연하여 비용을 절감할 수 있는 실제 객체를 나타내는데 좋다. 프록시는 일반적인 객체를 나타내고 네트워크 통신을 처리하는데 용이한 원격 서비스/API에 잘 동작한다.

위험요소

  • 자바의 동적 프록시는 클래스가 하나 이상의 인터페이스를 구현할 때만 동작한다. 프록시는 인터페이스를 구현함으로써 생성된다.

  • 감사, 인증과 같은 여러 책임을 처리할 때는 이러한 요구사항을 처리하는 단일 프록시를 가지는 게 더 좋다. 프록시가 객체를 생성하는 방법 때문에 관리하기가 어렵게 된다.

  • 정적 프록시는 데코레이터 & 어댑터 패턴과 꽤 비슷하게 보인다. 이런 패턴이 익숙치 않은 누군가에게 코드로 이해하기 어려울 수도 있다.



반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유