본문 바로가기

JAVA/디자인 패턴

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

반응형
구조 패턴(Structural Pattern)


프록시 패턴(Proxy Pattern)은 객체지향 디자인 패턴 중 하나로, 객체에 대한 접근을 제어하기 위해 사용됩니다. 객체에 대한 직접적인 접근 대신 프록시 객체를 통해 간접적으로 접근함으로써 객체의 동작을 제어하거나 객체에 대한 추가적인 작업을 수행할 수 있습니다.

프록시는 실제 객체와 같은 인터페이스를 제공하며, 실제 객체의 대리인 역할을 합니다. 프록시 객체는 실제 객체와 동일한 인터페이스를 구현하며, 클라이언트 코드가 프록시를 호출하면 프록시는 실제 객체를 호출하거나 호출하지 않을 수 있습니다. 이를 통해 실제 객체에 대한 접근을 제어할 수 있습니다.

프록시 패턴은 다양한 상황에서 사용됩니다. 예를 들어, 다음과 같은 경우에 프록시 패턴을 사용할 수 있습니다.

 

  • 원격 객체에 대한 접근을 제어할 때: 원격 서버에 있는 객체에 대한 직접적인 접근 대신, 클라이언트는 프록시 객체를 통해 원격 서버에 접근할 수 있습니다. 이를 통해 원격 서버와 통신하는 코드를 캡슐화하고, 보안성을 높일 수 있습니다.
  • 객체에 대한 접근 권한을 제한할 때: 객체에 대한 접근 권한을 가진 클라이언트만 프록시 객체를 통해 객체에 접근할 수 있습니다. 이를 통해 객체에 대한 보안성을 높일 수 있습니다.
  • 객체의 생성 및 초기화를 지연시킬 때: 객체의 생성 및 초기화 작업이 무거운 경우, 프록시 객체를 생성하여 객체의 생성 및 초기화 작업을 지연시킬 수 있습니다. 이를 통해 프로그램의 성능을 향상시킬 수 있습니다.
  • 객체에 대한 캐싱을 수행할 때: 객체에 대한 캐싱을 수행하기 위해, 프록시 객체를 생성하여 캐시를 관리할 수 있습니다. 이를 통해 객체에 대한 접근을 더욱 빠르게 처리할 수 있습니다.

이러한 방법으로 프록시 패턴은 객체지향 소프트웨어 디자인에서 유용하게 사용될 수 있습니다.

 

 

Proxy 패턴 예제

 

예제 시나리오는 사용자 정보를 가져오는 UserService 인터페이스와 이를 구현하는 UserServiceImp 클래스가 있다고 가정해보겠습니다. UserServiceImp 클래스는 매우 무거운 데이터베이스 쿼리를 수행하는 경우가 있습니다. 따라서 우리는 UserServiceImp 클래스에 대한 프록시를 구현하여 이를 개선할 수 있습니다.

Diagrams

 

 

User 클래스

 

public class User {
    private Long id;
    private String name;
    private String mail;

    public User(Long id, String name, String mail) {
        this.id = id;
        this.name = name;
        this.mail = mail;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getMail() {
        return mail;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", mail='" + mail + '\'' +
                '}';
    }
}



UserService 인터페이스

public interface UserService {
    public User getUserById(int id);
}



UserServiceImpl 클래스

public class UserServiceImpl implements UserService {
    @Override
    public User getUserById(int id) {
        // 매우 무거운 데이터베이스 쿼리를 수행한다고 가정합니다.
        // 이 부분을 개선하기 위해 프록시 패턴을 적용해볼 것입니다.
        return new User(id, "John Doe", "johndoe@example.com");
    }
}



UserProxy 클래스

public class UserProxy implements UserService {
    private UserService userService;

    public UserProxy() {
        userService = new UserServiceImpl();
    }

    @Override
    public User getUserById(int id) {
        // 캐시에 사용자 정보가 존재하는지 확인합니다.
        // 캐시에 정보가 있다면, 캐시에서 사용자 정보를 반환합니다.
        // 캐시에 정보가 없다면, UserServiceImpl 객체를 이용하여 사용자 정보를 가져옵니다.
        // 이후, 가져온 사용자 정보를 캐시에 저장하고, 사용자 정보를 반환합니다.
        return userService.getUserById(id);
    }
}


위의 예제에서는 UserProxy 클래스를 구현하여 UserService 인터페이스를 구현합니다. UserProxy 클래스는 UserServiceImpl 클래스의 인스턴스를 생성하고, getUserById() 메서드를 호출할 때 캐시에 사용자 정보가 존재하는지 확인합니다. 캐시에 정보가 있으면, 캐시에서 사용자 정보를 반환하고, 그렇지 않으면 UserServiceImpl 객체를 이용하여 사용자 정보를 가져옵니다. 가져온 사용자 정보를 캐시에 저장하고, 사용자 정보를 반환합니다.

이제 아래의 코드를 실행하여 프록시 패턴이 잘 작동하는지 확인해볼 수 있습니다.

public class Main {
    public static void main(String[] args) {
        UserService userService = new UserProxy();

        // 첫 번째 호출 - 캐시가 비어있으므로, UserServiceImpl 객체를 이용하여 사용자 정보를 가져옵니다.
        User user1 = userService.getUserById(1);

        // 두 번째 호출 - 캐시에 사용자 정보가 존재하므로, 캐시에서 사용자 정보를 반환합니다.
        User user2 = userService.getUserById(1);

        System.out.println(user1);
        System.out.println(user2);
    }
}

 

프록시 패턴은 객체지향 설계 원칙 중 하나인 개방-폐쇄 원칙(OCP, Open-Closed Principle)을 만족하며, 코드 재사용성과 유지보수성을 높이는 효과를 가지고 있습니다. 따라서 프록시 패턴은 다양한 상황에서 유용하게 사용될 수 있습니다.

반응형