개발/디자인패턴

[Java][디자인 패턴] 13. 프록시 패턴 (Proxy Pattern)

nova_dev 2022. 3. 17. 00:00
반응형

디자인패턴
[Java][디자인 패턴] 13. 프록시 패턴 (Proxy Pattern)

객체 접근을 제어하기 위해 중간 단계에 대리자를 위치시키는 패턴이다.

 

 프록시 패턴이란?

  • 프록시는 실제 객체를 호출하면 행위를 중간에 가로채서 다른 동작을 수행하는 객체로 변경한다.
  • 객체를 정교하게 제어해야 하거나 객체 참조가 필요한 경우 프록시 패턴을 사용한다.
  • 프록시 패턴의 적용 사례는 매우 다양하고 다른 패턴과 결합하여 사용되는 경우도 많다
  • 프록시 패턴의 특징은 투명성을 이용해 객체를 분리하여 재위임한다는 것이다.
  • 분리된 객체를 위임함으로써 대리 작업을 중간 단계에 삽입할 수도 있으며 분리된 객체를 동적으로 연결함으로써 객체의 실행 시점을 관리할 수도 있다.
  • 프록시 패턴은 세밀한 객체의 접근이 필요할 때도 매우 유용하다.

 

 프록시 패턴의 종류

  • 원격 프록시
    • 프록시 패턴을 가장 많이 응용하는 사레로 주로 데이터의 전달을 목적으로 사용
  • 가상 프록시
    • 프로그램의 실행 속도를 개선하기 위한 패턴
    • 프록시 패턴을 이용해서 무거운 객체 생성을 유보한다.
  • 보호 프록시
    • 통제 제어 목적으로 사용하는 프록시
    • 객체 접근 제어를 위해 객체의 대리자 혹은 표시자를 제공한다.
  • 스마트 참조자 프록시
    • 실제 객체에 접근할 때 추가 행위를 부여하여 호출한다.
    • 장식자 패턴과 유사하게 객체를 동적으로 확장할 때 응용한다.
  • 방화벽, 레퍼런스, 동기화 등등

 

 프록시 패턴 구조

에 있는 예시 코드가 이해하기 쉬운 듯하여 이 예제는 직접 짜지 않고 책에 있는 PHP 코드를 Java 코드로 변경하여 구현했다.

 프록시 패턴 코드

1. 각 행동에 대한 인증 enum 클래스

public enum Permit {
    ACTION1, ACTION2;
}

2. Subject 인터페이스 (프록시와 실제 객체가 사용할 수 있는 메서드를 정의)

public interface Subject {
    String action1();

    String action2();

    boolean isProxy();
}

3. RealSubject 클래스 (Subject를 구현한 진짜 행동을 하는 클래스)

public class RealSubject implements Subject {

    public RealSubject() {
        System.out.println(this.getClass().getName() + " 객체 생성");
    }

    public String action1() {

        System.out.println(this.getClass().getName() + ": action1");
        return (this.getClass().getSimpleName() + ": action1");
    }

    public String action2() {
        System.out.println(this.getClass().getName() + ": action2");
        return (this.getClass().getSimpleName() + ": action2");
    }

    @Override
    public boolean isProxy() {
        return false;
    }
}

4. Proxy 클래스 (Subject를 구현한 프록시 클래스)

public class Proxy implements Subject {

    private Subject subject;
    private Permit permit;

    public int getAction1Count() {
        return action1Count;
    }

    private int action1Count;

    public Proxy(Permit permit) {
        System.out.println(this.getClass().getName() + " 객체 생성(레이지 로딩)");
        this.permit = permit;
        this.action1Count = 0;
    }

    public Proxy(Subject subject) {
        System.out.println(this.getClass().getName() + " 객체 생성");
        this.subject = subject;
    }

    @Override
    public String action1() {
        System.out.println(this.getClass().getName() + ": action1");
        action1Count++;
        return "프록시로 action1 메서드 대체";
    }

    @Override
    public String action2() {
        System.out.println(this.getClass().getName() + ": action1");
        if (Permit.ACTION2 != this.permit) {
            return "action2 의 실행 권한이 없습니다.";
        }
        if (this.subject == null) {
            this.real();
        }
        return this.subject.action2();
    }

    private void real() {
        this.subject = new RealSubject();
    }

    public boolean isProxy() {
        return true;
    }
}

5. 프록시 팩토리 클래스

public class ProxyFactory {
    public Subject getObject() {
        return new Proxy(new RealSubject());
    }
}

6. Real Subject 테스트 코드

class RealSubjectTest {
    @Test
    @DisplayName("RealSubject 바로 호출하기")
    void action() {
        RealSubject realSubject = new RealSubject();
        realSubject.action1();
        realSubject.action2();
    }
}

결과

design.pattern.ch13.proxy.basic.RealSubject 객체 생성
design.pattern.ch13.proxy.basic.RealSubject: action1
design.pattern.ch13.proxy.basic.RealSubject: action2

6.Proxy 테스트 코드

class ProxyTest {

    @Test
    @DisplayName("프록시 객체 테스트")
    void action() {
        ProxyFactory proxyFactory = new ProxyFactory();
        Subject subject = proxyFactory.getObject();
        subject.action1();
        subject.action2();
    }

    @Test
    @DisplayName("프록시 레이지로딩 테스트 (ACTION1 권한)")
    void actionLazyLoadingPermit1() {
        Proxy proxy = new Proxy(Permit.ACTION1);
        assertEquals("프록시로 action1 메서드 대체", proxy.action1());
        assertEquals("action2 의 실행 권한이 없습니다.", proxy.action2());
    }

    @Test
    @DisplayName("프록시 레이지로딩 테스트 (ACTION2 권한)")
    void actionLazyLoadingPermit2() {
        Proxy proxy = new Proxy(Permit.ACTION2);
        assertEquals("프록시로 action1 메서드 대체", proxy.action1());
        assertEquals("RealSubject: action2", proxy.action2());
    }

    @Test
    @DisplayName("프록시의 action1 카운트 수 구하기")
    void actionProxyCount() {
        Proxy proxy = new Proxy(Permit.ACTION1);
        proxy.action1();
        proxy.action1();
        proxy.action1();
        proxy.action1();
        assertEquals(4, proxy.getAction1Count());
    }
}
# 프록시 객체 테스트 결과
design.pattern.ch13.proxy.basic.RealSubject 객체 생성
design.pattern.ch13.proxy.basic.Proxy 객체 생성
design.pattern.ch13.proxy.basic.Proxy: action1
design.pattern.ch13.proxy.basic.Proxy: action1

# 프록시 레이지 로징 테스트 (Action1 권한)
design.pattern.ch13.proxy.basic.Proxy 객체 생성(레이지 로딩)
design.pattern.ch13.proxy.basic.Proxy: action1
design.pattern.ch13.proxy.basic.Proxy: action1

# 프록시 레이지 로딩 테스트 (Action2 권한)
design.pattern.ch13.proxy.basic.Proxy 객체 생성(레이지 로딩)
design.pattern.ch13.proxy.basic.Proxy: action1
design.pattern.ch13.proxy.basic.Proxy: action1
design.pattern.ch13.proxy.basic.RealSubject 객체 생성
design.pattern.ch13.proxy.basic.RealSubject: action2

# 프록시 action1 카운트 수 구하기
design.pattern.ch13.proxy.basic.Proxy 객체 생성(레이지 로딩)
design.pattern.ch13.proxy.basic.Proxy: action1
design.pattern.ch13.proxy.basic.Proxy: action1
design.pattern.ch13.proxy.basic.Proxy: action1
design.pattern.ch13.proxy.basic.Proxy: action1

 

 Github 코드

Github 예제 코드 링크

Github 예제 테스트 코드 링크

참고 자료

 

 

 

 

 

반응형