Don't give up!

[Design Pattern] 프록시 패턴(Proxy Pattern) 본문

개발서적/Head First Design Patterns

[Design Pattern] 프록시 패턴(Proxy Pattern)

Heang Lee 2021. 12. 26. 22:53
이 글은 에릭 프리먼의 'Head First Design Patterns'를 읽고 TIL(Today I Learned)로써 정리한 내용을 기록하는 글입니다.
자세한 내용은 책을 통해 확인하실 수 있습니다.

알라딘: Head First Design Patterns (aladin.co.kr)

 

Head First Design Patterns

볼 거리가 많고 재미있으면서도, 머리 속에 쏙쏙 들어오는 방식으로 구성된 Head First 시리즈. 패턴의 근간이 되는 객체지향 디자인 원칙, 중요한 패턴, 디자인 적용 방법, 쓰지 말아야 하는 이유

www.aladin.co.kr

프록시 패턴 (Proxy Pattern)

프록시 패턴은 객체에 대한 접근을 제어하기 위해 대리인이나 대변인에 해당하는 객체를 제공하는 패턴입니다.

객체를 사용하고자 할 때, 직접적으로 객체를 참조하지 않고 Proxy 객체를 통해 대상 객체에 접근함으로써 객체가 클라이언트의 힙에 존재하지 않아도 제어할 수 있습니다.

프록시 패턴은 다음의 종류가 존재합니다.

- 원격 프록시(Remote Proxy) : 다른 JVM에 들어있는 객체에 대한 접근을 제어할 수 있도록 합니다.

- 가상 프록시(Virtual Proxy) : 진짜 객체가 생성되기 전까지 해당 객체를 대신함으로써 생성하기 힘든 자원에 대한 접근을 제어할 수 있습니다. 

- 보호 프록시(Access Proxy) : 접근 권한이 필요한 자원에 대한 접근을 제어할 수 있습니다.

- 방화벽 프록시 : 네트워크 자원에 대한 접근을 제어합니다.

- 스마트 레퍼런스 프록시(Smart Reference Proxy) : 주 객체가 참조될 때마다 추가 행동을 제공합니다.

- 캐싱 프록시(Caching Proxy) : 비용이 많이 드는 작업의 결과를 임시 저장합니다. 여러 클라이언트에서 결과를 공유하게 함으로써 계산 시간 또는 네트워크 지연을 줄여주는 효과가 있습니다.

- 동기화 프록시(Synchronization Proxy) : 여러 스레드에서 객체에 접근하는 경우에 작업을 처리할 수 있게 합니다.

- 복잡도 숨김 프록시(Complexity Hiding Proxy) : 복잡한 클래스 집합에 대한 접근을 제어하고, 복잡도를 숨깁니다.

- 지연 복사 프록시(Copy-On-Write Proxy) : 클라이언트에서 필요로 할 때까지 객체가 복사되는 것을 지연시킴으로써 객체의 복사를 제어합니다.

자바의 동적 프록시(Java Dynamic Proxy)

자바의 java.lang.reflect 패키지에는 Proxy 클래스가 내장되어 있습니다. 

Proxy 클래스의 기능들을 활용하면 실행 중에 프록시 객체를 생성할 수 있으며 이러한 자바 기술을 동적 프록시(dynamic proxy)라고 합니다.

다음은 동적 프록시를 사용하여 구현한 보호 프록시의 코드입니다.

//subject 인터페이스
public interface PersonBean {
    String getName();
    String getGender();
    String getInterests();
    int getHotOrNotRating();
    
    void setName(String name);
    void setGender(String gender);
    void setInterests(String interests);
    void setHotOrNotRadting(int rating);
}

//실제 subject 클래스
public class PersonBeanImpl implements PersonBean {
    String name;
    String gender;
    String interests;
    int rating;
    int ratingCount = 0;
    
    public String getName() {
        return name;
    }
    
    public String getGender() {
        return interests;
    }
    
    public in getHotOrNotRating() {
        if (ratingCount == 0) return 0;
        return (rating / ratingCount);
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public void setGender(String gender) {
        this.gender = gender;
    }
    
    public void setInterests(String interests) {
        this.interests = interests;
    }
    
    public void setHotOrNotRating(int rating) {
        this.rating += rating;
        ratingCount++;
    }
}

//java.lang.reflect 패키지에서 제공하는 InvocationHandler 인터페이스를 구현하여 proxy를 생성하는 핸들러 구현
public class OwnerInvocationHandler implements InvocationHandler {
    PersonBean person;
    
    public OwnerInvocationHandler(PersonBean person) {
        this.person = person;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args) throws IllegalAccessException {
        try {
            if (method.getName().startsWith("get")) {
                return method.invoke(person, args);
            } else if (method.getName().equals("setHotOrNotRating")) {
                throw new IllegalAccessException();
            } else if (method.getName().startsWith("set")) {
                return method.invoke(person, args);
            }
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }
}

//프록시를 활용하는 클라이언트 클래스
public class MatchMakingTestDrive {
    public static void main(String[] args) {
        MatchMakingTestDrive test = new MatchMakingTestDrive();
        test.drive();
    }
    
    public MatchMakingTestDrive() {
        initializeDatabase();
    }
    
    public void drive() {
        PersonBean joe = getPersonFromDatabase("Joe Javabean");
        PersonBean ownerProxy = getOwnerProxy(joe);
        System.out.println("Name is " + ownerProxy.getName());
        ownerProxy.setInterests("bowling, Go");
        System.out.println("Interests set from owner proxy");
        try {
            ownerProxy.setHotOrNotRating(10);
        } catch (Exception e) {
            System.out.println("Can't set rating from owner proxy");
        }
        System.out.println("Rating is " + ownerProxy.getHotOrNotRating());
    }
    
    //subject의 classloader, subject의 interface, 그리고 subject의 InvoactionHadler 객체를 인자로 하여 프록시 객체를 생성하는 Proxy의 newProxyInstance 메소드
    PersonBean getOwnerProxy(PersonBean person) {
        return (PersonBean) Proxy.newProxyInstance(
        person.getClass().getClassLoader(),
        person.getClass().getInterfaces(),
        new OwnerInvocationHandler(person));
    }
}