C++ Proxy 패턴

실제 객체에 대한 대리자를 제공하여 접근을 제어합니다. 가상 프록시(지연 로딩)와 보호 프록시(접근 권한)를 통해 리소스 낭비 없이 안전한 객체 접근을 구현합니다.

Gist
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <stdexcept>
#include <chrono>

// ──────────────────────────────────────────────
// 주제 인터페이스: 실제 객체와 프록시가 공유
// ──────────────────────────────────────────────
class Image {
public:
    virtual ~Image() = default;
    virtual void render() const = 0;
    virtual std::string name() const = 0;
    virtual std::size_t sizeBytes() const = 0;
};

// ──────────────────────────────────────────────
// 실제 객체: 고해상도 이미지 (로딩 비용이 큼)
// ──────────────────────────────────────────────
class HighResolutionImage : public Image {
public:
    explicit HighResolutionImage(const std::string& filePath)
        : filePath_(filePath) {
        loadFromDisk(); // 생성 시 즉시 디스크 I/O 발생
    }

    void render() const override {
        std::cout << "  [HighResolutionImage] '" << filePath_
                  << "' 렌더링 완료 (" << sizeBytes_ / 1024 << " KB)\n";
    }

    std::string name() const override { return filePath_; }
    std::size_t sizeBytes() const override { return sizeBytes_; }

private:
    void loadFromDisk() {
        // 실제로는 파일 I/O 수행 — 여기선 시뮬레이션
        std::cout << "  [HighResolutionImage] 디스크에서 로딩: "
                  << filePath_ << " (대용량 파일 — 시간 소요)\n";
        sizeBytes_ = 8 * 1024 * 1024; // 8MB 시뮬레이션
    }

    std::string filePath_;
    std::size_t sizeBytes_{0};
};

// ──────────────────────────────────────────────
// [가상 프록시] 지연 로딩(Lazy Loading)
// 실제로 render() 호출 시점까지 HighResolutionImage 생성 지연
// ──────────────────────────────────────────────
class LazyImageProxy : public Image {
public:
    explicit LazyImageProxy(std::string filePath)
        : filePath_(std::move(filePath)) {}

    void render() const override {
        // 최초 접근 시에만 실제 이미지 로드 (지연 초기화)
        if (!realImage_) {
            std::cout << "  [LazyImageProxy] 최초 접근 — 실제 이미지 로드 시작\n";
            realImage_ = std::make_unique<HighResolutionImage>(filePath_);
        } else {
            std::cout << "  [LazyImageProxy] 캐시된 이미지 사용\n";
        }
        realImage_->render();
    }

    std::string name() const override { return filePath_; }

    std::size_t sizeBytes() const override {
        // 아직 로드 안 됐으면 0 반환 (실제 로드 없이)
        return realImage_ ? realImage_->sizeBytes() : 0;
    }

private:
    std::string filePath_;
    mutable std::unique_ptr<HighResolutionImage> realImage_; // 지연 초기화
};

// ──────────────────────────────────────────────
// 데이터베이스 서비스 인터페이스
// ──────────────────────────────────────────────
enum class UserRole { GUEST, USER, ADMIN };

class DatabaseService {
public:
    virtual ~DatabaseService() = default;
    virtual std::string query(const std::string& sql) const = 0;
    virtual void execute(const std::string& sql) = 0;
};

class RealDatabaseService : public DatabaseService {
public:
    std::string query(const std::string& sql) const override {
        std::cout << "  [RealDB] 쿼리 실행: " << sql << "\n";
        return R"({"rows": [{"id":1,"name":"홍길동"}]})";
    }

    void execute(const std::string& sql) override {
        std::cout << "  [RealDB] 명령 실행: " << sql << "\n";
    }
};

// ──────────────────────────────────────────────
// [보호 프록시] 접근 권한 제어
// 역할(Role)에 따라 쿼리/실행 권한을 차별화
// ──────────────────────────────────────────────
class ProtectionDatabaseProxy : public DatabaseService {
public:
    ProtectionDatabaseProxy(std::shared_ptr<RealDatabaseService> db,
                            UserRole role)
        : realDb_(std::move(db)), role_(role) {}

    std::string query(const std::string& sql) const override {
        // GUEST 이상이면 읽기 허용
        if (role_ == UserRole::GUEST || role_ == UserRole::USER
            || role_ == UserRole::ADMIN) {
            logAccess("QUERY", sql);
            return realDb_->query(sql);
        }
        throw std::runtime_error("[보호 프록시] 읽기 권한 없음");
    }

    void execute(const std::string& sql) override {
        // ADMIN만 쓰기 허용
        if (role_ != UserRole::ADMIN) {
            std::cout << "  [ProtectionProxy] 접근 거부 — ADMIN 권한 필요\n";
            throw std::runtime_error("[보호 프록시] 쓰기 권한 없음: "
                                     + roleToString());
        }
        logAccess("EXECUTE", sql);
        realDb_->execute(sql);
    }

private:
    void logAccess(const std::string& type, const std::string& sql) const {
        std::cout << "  [ProtectionProxy] 접근 로그 [" << roleToString()
                  << "] " << type << ": " << sql << "\n";
    }

    std::string roleToString() const {
        switch (role_) {
            case UserRole::GUEST: return "GUEST";
            case UserRole::USER:  return "USER";
            case UserRole::ADMIN: return "ADMIN";
        }
        return "UNKNOWN";
    }

    std::shared_ptr<RealDatabaseService> realDb_;
    UserRole role_;
};

// ──────────────────────────────────────────────
// 사용 예시
// ──────────────────────────────────────────────
int main() {
    std::cout << "=== Proxy 패턴 ===\n\n";

    // ── 가상 프록시: 지연 로딩 ──
    std::cout << "[1] 가상 프록시 — 이미지 지연 로딩\n";
    LazyImageProxy proxy1("photo_4k_001.jpg");
    LazyImageProxy proxy2("photo_4k_002.jpg");

    std::cout << "  프록시 생성 완료 (아직 실제 이미지 미로드)\n";
    std::cout << "  proxy1 크기: " << proxy1.sizeBytes() << " bytes\n\n"; // 0

    std::cout << "  -- 첫 번째 render() 호출 --\n";
    proxy1.render(); // 이 시점에 실제 로드

    std::cout << "\n  -- 두 번째 render() 호출 (캐시 사용) --\n";
    proxy1.render(); // 캐시에서 렌더링

    // ── 보호 프록시: 접근 제어 ──
    std::cout << "\n[2] 보호 프록시 — 역할 기반 접근 제어\n";
    auto realDb = std::make_shared<RealDatabaseService>();

    ProtectionDatabaseProxy adminProxy(realDb, UserRole::ADMIN);
    ProtectionDatabaseProxy userProxy(realDb, UserRole::USER);

    std::cout << "\n  [ADMIN 접근]\n";
    adminProxy.query("SELECT * FROM users");
    adminProxy.execute("DELETE FROM logs WHERE date < '2026-01-01'");

    std::cout << "\n  [USER 접근]\n";
    userProxy.query("SELECT id, name FROM users");
    try {
        userProxy.execute("DROP TABLE users"); // 권한 없음 → 예외
    } catch (const std::exception& e) {
        std::cout << "  예외 처리: " << e.what() << "\n";
    }

    return 0;
}