[디자인패턴] 구조패턴(3) - Proxy Pattern (프록시 패턴)

Study/Software Architecture · 2023. 5. 24. 12:55

디자인 패턴 총정리 시리즈의 여덟 번째 패턴은 Proxy Pattern입니다.

 

소프트웨어를 개발할 때 객체 간의 상호작용을 제어하거나 향상해야 하는 상황이 있습니다. 그런 경우 Proxy 패턴을 사용할 수 있습니다. 

 

해당 게시물에서 이러한 Proxy 패턴에 대한 간단한 개념과 사용 예시, 장/단점을 알아보겠습니다.

 

1. 개요

Proxy 패턴은 다른 객체의 접근을 제어하거나 기능을 추가하기 위한 대리 객체 혹은 Place holder를 제공할 수 있습니다. 클라이언트와 실제 객체의 중간 역할을 하는 Proxy 객체를 통해 객체의 동작을 보다 편리하게 관리할 수 있게 됩니다.

 

해당 패턴은 Client, Subject, RealSubject, Proxy 4가지 주요 컴포넌트로 구성되어 있습니다.

 

 

2. 예시

다음은 Proxy 패턴을 사용하여 사용자 정보를 얻기 위한 원격 API 서비스 시나리오를 생각해 보겠습니다. 해당 시나리오에서는 성능을 향상하고 API 호출을 줄이기 위한 캐시 메커니즘을 추가하려 합니다. 이때 캐시를 처리하고 필요한 경우에만 실제 API 서비스에 요청을 위임하는 Proxy 객체를 생성합니다.

 

#include <iostream>
#include <unordered_map>

// Subject Interface: Remote API Service
class RemoteService {
public:
    virtual std::string getUserInfo(int userId) = 0;
};

// Real Subject: Remote API Service Implementation
class RemoteApiService : public RemoteService {
public:
    std::string getUserInfo(int userId) override {
        // Simulate API call
        std::cout << "Making API call to fetch user information for user ID: " << userId << std::endl;
        // ... Fetch user info from remote API and return
        return "User information for ID " + std::to_string(userId);
    }
};

// Proxy: Cached Remote Service
class CachedRemoteService : public RemoteService {
private:
    RemoteApiService* remoteService;
    std::unordered_map<int, std::string> cache;

public:
    CachedRemoteService() : remoteService(new RemoteApiService()) {}

    std::string getUserInfo(int userId) override {
        // Check if the result is already cached
        if (cache.find(userId) != cache.end()) {
            std::cout << "Retrieving user information from cache for user ID: " << userId << std::endl;
            return cache[userId];
        }

        // If not cached, fetch from the remote service and cache the result
        std::string userInfo = remoteService->getUserInfo(userId);
        cache[userId] = userInfo;
        return userInfo;
    }

    ~CachedRemoteService() {
        delete remoteService;
    }
};

int main() {
    // Client code using the Proxy Pattern
    RemoteService* remoteService = new CachedRemoteService();

    // Fetch user information
    std::cout << remoteService->getUserInfo(1) << std::endl; // Calls the remote API
    std::cout << remoteService->getUserInfo(1) << std::endl; // Retrieves from cache
    std::cout << remoteService->getUserInfo(2) << std::endl; // Calls the remote API
    std::cout << remoteService->getUserInfo(2) << std::endl; // Retrieves from cache

    delete remoteService;
    return 0;
}

 

해당 예시 시나리오에서 원격 API 서비스를 나타내는 RemoteService 인터페이스가 있습니다. RemoteApiService 클래스는 해당 인터페이스를 구현하고 사용자 정보를 얻기 위한 getUserInfo 메서드를 오버라이딩 하여 실제로 구현합니다.

 

CachedREmoteService 클래스는 프록시 역할을 하며 RemoteService 인터페이스를 멤버 변수로 갖고, getUserInfo에 RemoteService의 getUserInfo 내용을 포함합니다. 이를 통해 실제 서비스(RemoteApiService) 인스턴스를 유지하고 이미 취득한 사용자 정보를 저장하는 캐시를 관리할 수 있습니다.

 

3. 장점

Enhanced Control: Proxy 패턴을 사용하면 실제 객체에 대한 접근을 제어할 수 있습니다. 이를 통해 Indirection 레이어가 제공되며, 요청을 실제 객체로 전송하기 전/후 로직을 추가할 수 있습니다.

Improved Performance: Proxy는 cache, lazy loading 등 최적화를 구현하여 성능 향상에 도움을 줄 수 있습니다. 이를 통해 비용이 많이 드는 작업을 최소화하고, 네트워크 호출을 줄일 수 있어 응답 시간을 단축시킬 수 있습니다.

Security and Access: Proxy는 실제 객체에 대한 접근을 허용학 전에 인증 등의 접근 제어 로직을 적용할 수 있습니다.

 

4. 단점

Increased Complexity: Proxy를 도입하면 추가 클래스와 Indirection이 필요하기 때문에 시스템이 복잡해질 수 있습니다.

Synchronization: 여러 클라이언트가 동시에 Proxy에 접근하는 경우 cache와 같은 공유 리소스를 처리하는 스레드 안정성을 관리할 동기화 로직이 추가되어야 할 수 있습니다.

 

5. 결론

Proxy 패턴은 객체 접근 제어, 기능 추가 및 객체 상호 간 최적화를 가능하게 합니다. Proxy를 도입하여 프로그램의 성능을 향상하고, 캐싱 구현 및 보안 절차 등을 수행할 수 있지만, 추가적인 복잡성과 동기화 문제를 고려하여 사용해야 합니다.

반응형