ios dev kangwook.

14. Proxy Pattern 본문

Design Pattern

14. Proxy Pattern

kangwook 2022. 9. 6. 20:52

Proxy Pattern은 어떤 객체를 사용하고자 할 때 객체를 직접적으로 참조하는 것이 아닌 해당 객체를 대신하는 객체를 통해 대상 객체에 접근하는 패턴

Proxy Pattern

Proxy Pattern은 다른 객체에 대한 접근을 제어할 대리 또는 placeholder를 제공한다.

 

  • Subject
    • Proxy와 RealSubject가 구현해야하는 인터페이스
    • 두 객체를 동일하게 다룸
  • RealSubject
    • 실질적으로 요청에 대해 주된 기능을 수행하는 객체
    • Proxy객체는 내부적으로 이 객체를 로직에 맞게 사용
  • Proxy
    • Subject를 구현함으로써 클라이언트는 RealSubject 사용하는 것과 별 차이가 없어야 함

Proxy Pattern 예제

  • 자바의 reflect 패키지에 내장되어있는 Proxy 클래스를 이용해 매칭 서비스 개발
  • Subject 인터페이스 : PersonBeanNo Quarter : 동전 없음
public interface PersonBean {
    String getName();
    String getGender();
    String getInterests();
    int getHotOrNotRating();
 
    void setName(String name);
    void setGender(String gender);
    void setInterests(String interests);
    void setHotOrNotRating(int rating);
}
  • RealSubject 클래스 : PersonBeanImpl
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 int 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++;
    }
}
  • Proxy 클래스 : OwnerInvocationHandler
    • InvocationHandler 에서는 프록시의 행동을 구현
    • 프록시 클래스 및 객체를 만드는 일은 자바에서 알아서 해주기 때문에 프록시의 메소드가 호출되었을 때 할 일을 지정해주는 핸들러만 만들면 됨
    • 프록시의 메소드가 호출되면 프록시에서는 그 호출을 호출 핸들러에게 넘기고 호출 핸들러는 호출된 메서드가 무엇이든 invoke() 메소드를 호출

import java.lang.reflect.*;
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;
    }
}
  • Client 클래스 : Main 함수에서 테스트
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());
 
        PersonBean nonOwnerProxy = getNonOwnerProxy(joe);
        System.out.println("Name is " + nonOwnerProxy.getName());
        try {
            nonOwnerProxy.setInterests("bowling, Go");
        } catch (Exception e) {
            System.out.println("Can't set interests from non onwer proxy");
        }
        nonOwnerProxy.setHotOrNotRating(3);
        System.out.println("Rating set from non owner proxy");
        System.out.println("Rating is " + nonOnwerProxy.getHotOrNotRating());
    }
    // subject의 classloader, subject의 interface,
        // 그리고 subject의 InvocationHandler객체를 인자로 받아서 프록시 객체를 생성
    PersonBean getOwnerProxy(PersonBean person) {
        return (PersonBean)Proxy.newProxyInstance(person.getClass().getClassLoader(),
                                                  person.getClass().getInterfaces(),
                                                  new OwnerInvocationHandler(person));
    }
        // getNonOwnerProxy(PersonBean person) 등 추가적으로 구현
}

조금 더 직관적인 예제 : Displaying Image

  • Subject : Image Interface
public interface Image {
    void displayImage();
}
  • RealSubject : RealImage class
public class RealImage implements Image {
    private String fileName;
 
    public RealImage(String fileName) {
        this.fileName = fileName;
        loadFromDisk(fileName);
    }
     
    private void loadFromDisk(String fileName) {
        System.out.println("Loading " + fileName);
    }
     
    @Override
    public void displayImage() {
        System.out.println("Displaying " + fileName);
    }
}
  • Proxy : ProxyImage class
public class ProxyImage implements Image {
    private RealImage realImage;
    private String fileName;
     
    public ProxyImage(String fileName) {
        this.fileName = fileName;
    }
     
    @Override
    public void displayImage() {
        if (realImage == null) {
            realImage = new RealImage(fileName);
        }
        realImage.displayImage();
    }
}
  • Client : ProxyMain class
public class ProxyMain {
    public static void main(String[] args) {
        Image image1 = new ProxyImage("test1.png");
        Image image2 = new ProxyImage("test2.png");
         
        image1.displayImage();
        System.out.println();
        image2.displayImage();
    }
}
  • 결과
Loading test1.png
Displaying test1.png

Loading test2.png
Displaying test2.png
  • ProxyMain에서 RealImage에 직접 접근하지 않고 ProxyImage 객체를 생성하여 대신 시키게 됨
  • Proxy는 displayImage() 메서드를 호출하고 그 반환값을 Main에 반환함

Proxy의 종류

  • 원격 프록시
    • 클라이언트와 원격 객체 사이의 데이터 전달을 관리해줌
  • 가상 프록시
    • 인스턴스를 생성하는데 비용이 많이 드는 객체에 대한 접근을 제어
  • 보호 프록시
    • 호출한 쪽의 권한에 따라 객체에 있는 메소드에 대한 접근을 제어

결론

  • Proxy Pattern은 어떤 다른 객체로 접근하는 것을 통제하기 위해서 그 객체의 대리자나 자리표시자(placeholder)의 역할을 하는 객체를 제공하는 구조 패턴(Structural Pattern)
  • 비슷한 패턴들과의 비교
    • Decorator : 다른 객체를 감싸서 새로운 행동을 추가해줌
    • Facade : 여러 객체를 감싸서 인터페이스를 단순화 시킴
    • Adapter : 다른 객체를 감싸서 다른 인터페이스를 제공
    • Proxy : 다른 객체를 감싸서 접근을 제어
  • 장점
    • 사이즈가 큰 객체가 로딩되기 전에도 프록시를 통해 참조할 수 있음
    • 로컬에 있지 않고 떨어져 있는 객체를 사용할 수 있음
    • 원래 객체에 접근하는 것에 대해 사전 처리를 할 수 있음
  • 단점
    • 객체를 생성할 때 한 단계 거치게 되므로, 빈번한 객체 생성이 필요한 경우 성능이 저하될 수 있음
    • 로직이 난해해져 가독성이 떨어질 수 있음
 

 

 


본문은 Head First Design Pattern (2004) 를 참고하여 공부하고 작성하였음


 

'Design Pattern' 카테고리의 다른 글

15. Compound Pattern  (1) 2022.09.07
13. State Pattern  (0) 2022.09.05
12. Composite Pattern  (0) 2022.09.04
11. Iterator Pattern  (0) 2022.08.31
10. Template Method Pattern  (0) 2022.08.29
Comments