프록시 패턴

Groot·2023년 12월 10일
0

TIL

목록 보기
145/148
post-thumbnail

프록시 패턴

  • 다른 객체에 대한 대리자(Proxy) 또는 대변자 역할을 하는 객체를 제공하여 해당 객체에 대한 접근을 제어하거나 보완하는 데 사용
  • 프록시는 실제 객체에 대한 인터페이스 역할을 하면서, 실제 객체에 대한 접근을 제어하거나 필요한 추가 기능을 제공할 수 있다.
  • 객체의 생성 시점을 지연시키거나, 접근 제어, 캐싱, 로깅, 인증, 네트워크 통신 등의 기능을 추가할 수 있다.

활용성

  • 원격지 프록시
    • 서로 다른 주소 공간에 존재하는 객체를 가리키는 대표 객체
  • 가상 프록시
    • 요청이 있을 때만 필요한 고비용 객체를 생성
  • 보호용 프록시
    • 원래 객체에 대한 실제 접근을 제어
    • 객체별로 접근 제어 권한이 다를 때 유용

구조

요소

  • Proxy
    • 실제로 참조할 대상에 대한 참조자 관리
      • Service와 동일한 인터페이스를 제공하여 대체 될 수 있어야함
      • 실제 대상에 대한 접근을 제어하고 실제 대상의 생성과 삭제를 책임진다.
      • 원격지 프록시
        • 요청 메시지와 인자를 인코딩하여 이를 다른 주소 공간에 있는 실제 대상에게 전달
      • 가상의 프록시
        • 실제 대상에 대한 추가적 정보를 보유하여 실제 접근을 지연할 수 있어야 함.
      • 보호용 프록시
        • 요청한 대상이 실제 요청할 수 있는 권한이 있는지 확인
  • Service
    • RealService와 Proxy에 공통적인 인터페이스를 정의하여, RealService가 요청되는 곳에 Proxy를 사용할 수 있게 함.
  • RealService
    • Proxy가 대표하는 실제 객체

협력 방법

  • 프록시는 자신이 받은 요청을 RealService 객체에 전달.

특징

  • 원격지 프록시는 객체가 다른 주소 공간에 존재한다는 사실을 숨길 수 있다.
  • 가상 프록시는 요구에 따라 객체를 생성하는 등 처리를 최적화할 수 있다.
  • 보호용 프록시는 객체가 접근할 때마다 추가 관리를 책임, 객체를 생성할 것인지 삭제할 것인지를 관리
    • 프록시 패턴이 사용자에게 숨길 수 있는 또 다른 최적화는 copy-on-write (요구가 들어올 때만 객체를 생성하는 개념) → 프록시를 사용해서 복사 절차를 미루고 사본이 수정될 때만 실제 복사
  • 개방/폐쇄 원칙

단점

  • 서비스의 응답이 늦어질 수 있다.
  • 코드가 복잡해짐.

예시 코드

import Foundation
// MARK: - 보호
// 서비스 인터페이스
protocol DataService {
    func fetchData() -> String
}

// 리얼 서비스
class RealDataService: DataService {
    func fetchData() -> String {
        return "실제 데이터를 가져옴"
    }
}

// 데이터 프록시
class DataProxy: DataService {
    private var realService: RealDataService
    
    init(_ realService: RealDataService) {
        self.realService = realService
    }
    
    func fetchData() -> String {
        if checkAccess() {
            let data = realService.fetchData()
            logAccess()
            return data
        } else {
            return "접근이 거부되었습니다."
        }
    }
    
    private func checkAccess() -> Bool {
        // 예시로 간단하게 모든 요청을 허용하는 것으로 구현
        return true
    }
    
    private func logAccess() {
        print("Proxy: Logging the time of request.")
    }
}

// 클라이언트 코드
class Client {
    static func clientCode(service: DataService) {
        let data = service.fetchData()
        print("Client: Data received - \(data)")
    }
}

print("Client: Executing the client code with a real service:")
let realService = RealDataService()
Client.clientCode(service: realService)

print("\nClient: Executing the same client code with a proxy:")
let proxy = DataProxy(realService)
Client.clientCode(service: proxy)

// MARK: 캐시
// 네트워크 요청을 처리하는 서비스 인터페이스
protocol NetworkService {
    func fetchData(url: URL) -> String
}

// 실제 네트워크 요청을 처리하는 클래스
class RealNetworkService: NetworkService {
    func fetchData(url: URL) -> String {
        // 네트워크 요청을 시뮬레이션하기 위해 URL의 문자열을 반환합니다.
        return "실제로 \(url)에서 데이터를 가져옴"
    }
}

// 네트워크 요청을 캐싱하고 중복 요청 시 캐시된 데이터를 반환하는 프록시 클래스
class NetworkServiceCacheProxy: NetworkService {
    private let realService = RealNetworkService() // 실제 서비스 객체
    private var cache = [URL: String]() // 요청 결과를 캐싱하는 딕셔너리
    
    func fetchData(url: URL) -> String {
        if let cachedData = cache[url] {
            print("Proxy: Returning cached data for URL - \(url)")
            return cachedData
        } else {
            print("Proxy: Requested URL - \(url), fetching data...")
            let newData = realService.fetchData(url: url)
            cache[url] = newData // 결과를 캐시에 저장
            return newData
        }
    }
}

// 클라이언트 코드
let client = NetworkServiceCacheProxy()

let url = URL(string: "https://www.example.com/api/data")!
let data1 = client.fetchData(url: url)
print(data1) // "실제로 https://www.example.com/api/data에서 데이터를 가져옴"

// 같은 URL로 다시 요청 (캐시된 데이터를 반환)
let data2 = client.fetchData(url: url)
print(data2) // "실제로 https://www.example.com/api/data에서 데이터를 가져옴" (캐시된 데이터를 반환)

참고

profile
I Am Groot

0개의 댓글