[디자인 패턴] Proxy Pattern

전성훈·2023년 11월 6일
0

DesignPattern

목록 보기
13/18
post-thumbnail

주제: 부담 덜기 작업


일반적으로 프록시는 다른 무언가와 이어지는 인터페이스의 역할을 하는 클래스이다. 프록시는 어떠한 것(이를테면 네트워크 연결, 메모리안의 커다란 객체, 파일, 또 복제할 수 없거나 수요가 많은 리소스)과도 인터페이스의 역할을 수행할 수 있다.
프록시 패턴의 잘 알려진 예로는 참조 횟수 스마트 포인터 객체이다.
복합적인 오브젝트들의 다수의 복사본이 존재해야만 하는 상황에서 프록시 패턴은 애플리케이션의 메모리 사용량을 줄이기 뒤해서 플라이웨이트 패턴과 결합된 형태로 나올 수도 있다.

프록시 패턴이란

  • 프록시 패턴은 객제 지향 디자인 패턴 중 하나로, 어떤 다른 객체에 대한 인터페이스를 제공하는 패턴이다. 이 패턴은 원래 객체에 대한 액세스를 제어하거나, 원래 객체를 보호하거나, 원래 객체의 성능을 향상시키는 등의 목적으로 사용된다.
  • Proxy는 한국말로 대리라는 뜻을 가지고 있다. Proxy는 다른 누군가를 대신해서 그 역할을 수행하는 존재이다. Proxy는 최대한 자신이 할 수 있는 일을 처리 한 다음, 자신이 할 수 있는 범위를 넘어서게 되면 진짜 일을 하는 사람에게 요청한다.
  • 프록시 서버를 예로 들면, 프록시 서버는 웹서버의 부담을 줄여주기 위하여 자신이 웹서버 대신 할 수 있는 일을 최대한 처리한 다음 할 수 없는 일은 웹서버에게 넘기게 된다. 이러한 작업 방식을 프록시 디자인 패턴이라고 한다.

프록시 패턴의 구조

프록시

  • Proxy
    • 원래 객체에 대한 인터페이스 역할을 한다. 클라이언트는 Proxy를 통해 원래 객체에 액세스 한다.
    • 클라이언트의 요구를 할 수 있는 만큼 처리하고, 필요할 경우 RealSubject에게 처리를 맡긴다.
    1. Remote Proxy
      • 요청을 처리하고 서비스 객체에 이를 전달하는 역할을 담당한다.
    2. Virtual Proxy
      • 서비스 객체에 대한 정보를 캐싱하여 접근을 연기시킨다.
    3. Protection Proxy
      • 특정 작업을 요청한 객체가 해당 작업을 수행할 권한을 가지고 있는지 확인한다.
  • RealSubject
    • Proxy에서 요청이 들어왔을때 요청에 대한 응답을 처리한다.
  • Subject
    • Proxy(대리) 역할과 RealSubject(실제의 주체) 역할을 동일시 하기 위한 프로토콜을 선언한다. 이 덕분에 클라이언트(요청하는 쪽)는 둘의 역할 차이를 몰라도 된다.

장단점

장점

  • 원래 객체에 대한 액세스를 제어할 수 있다.
  • 원래 객체를 보호할 수 있다.
  • 원래 객체의 성능을 향상시킬 수 있다.
  • RealSubject의 메모리가 큰 인스턴스일 경우 해당 인스턴스를 생성하는 시점은 정말로 필요한 시점에 지연시킬 수 있다.
  • Proxy는 RealSubject가 준비되지 않았거나, 시용할 수 없는 경우에도 동작 한다.
  • 개방 폐쇄 원칙을 지켜 RealSubject나, 클라이언트를 변경하지 않고, 새로운 프록시를 도입할 수 있다.

단점

  • Proxy 클래스를 도입해야 함으로 코드가 복잡해 진다.
  • RealSubject의 응답이 지연될 수 있다.

결론

  • Proxy 패턴은 객체에 대한 액세스를 제어하거나, 객체를 보호하거나, 객체의 성능을 향상시키는 등의 목적으로 사용된다. 이 패턴은 코드의 복잡성을 증가시킬 수 있지만, 적절히 사용하면 애플리케이션의 성능을 크게 향상시킬 수 있다. 따라서 Proxy 패턴은 애플리케이션의 요구 사항과 성능 목표에 따라 적절히 사용해야 한다.

예시 코드

유튜브 영상 다운로드 로직

import UIKit

enum Auth {
    case owner
    case guest
}

class Client {
    var auth: Auth
    
    init(_ auth: Auth) {
        self.auth = auth
    }
}

// Subject
protocol YouTubeDowloadSubject {
    func downloadYoutubeVideos() async -> [String]
}

// realSubject
final class RealSubject: YouTubeDowloadSubject {
    func downloadYoutubeVideos() async -> [String] {
        // 유튜브 서버에서 비디오를 다운로드하는 부분
        return []
    }
}

// proxy
// proxy는 캐싱을 구현하여 요청을 중간에서 컨트롤 할 수 있다.
// 또한 클라이언트의 권한에 따라 제어를 할 수도 있다.
final class Proxy: YouTubeDowloadSubject {
    // 진짜 요청을 받아서 처리하는 객체, 정말로 사용할때만 초기화하기 위하여 lazy
    private lazy var realSubject = RealSubject()
    
    // 캐싱 구현
    private var videoCache = [String]()
    
    // 클라이언트 권한 받음
    private var client: Client
    
    init(_ client: Client) {
        self.client = client
    }
    
    func downloadYoutubeVideos() async -> [String] {
        guard client.auth == .owner else {
            print("비디오를 다운로드할 권한이 없습니다.")
            return []
        }
        
        // 비디오 캐시가 비어있으면 실제 realSubject에 데이터 요청
        if videoCache.isEmpty {
            videoCache = await realSubject.downloadYoutubeVideos()
            return videoCache
        } else {
            // 비디오 캐시에 데이터가 있으면 리턴
            return videoCache
        }
    }
}

let client = Client(.owner)
let proxy = Proxy(client)

func loadYouTubeVideo(_ service: YouTubeDowloadSubject) {
    service.downloadYoutubeVideos()
}

loadYouTubeVideo(proxy)

이미지 로딩

이미지 로딩

  • 이 다이어그램은 클라이언트가 이미지를 표시하려고 할 때 Proxy가 실제 이미지를 로드하고, 이미지 데이터를 클라이언트에게 반환하여 표시하는 과정을 보여준다.

인터페이스 정의

protocol Image { 
	func displayImage()
}

RealImage 클래스 구현

class RealImage: Image { 
	private let filename: String
	
	init(filename: String) { 
		self.filename = filename
		loadImageFromDisk()
	}
	
	func loadImageFromDisk() { 
		print("Loading Image: \(filename)")
	}
	
	func displayImage() { 
		print("Display Image: \(filename)")
	}
}

ProxyImage 클래스 구현

  • 이 클래스는 'RealImage'의 프록시로, 실제 이미지를 처음 표시할 때만 로드하고, 그 이후에는 로드된 이미지를 표시한다.
class ProxyImage: Image { 
	private let filename: String 
	private var realImage: RealImage?
	
	init(filename: String) { 
		self.filename = filename
	}
	
	func displayImage() { 
		if realImage == nil { 
			realImage = RealImage(filename: filename)
		}
		realImage?.displayImage()
	}
}

Client 구현

let image1: Image = ProxyImage(filename: "image1.jpg")
image1.displayImage()  // 이미지를 로드하고 표시합니다.

let image2: Image = ProxyImage(filename: "image2.jpg")
image2.displayImage()  // 이미지를 로드하고 표시합니다.

image1.displayImage()  // 이미 로드된 이미지를 표시합니다.
  • 이 코드에서는 'image1'과 'image2'가 처음 표시될 때만 실제로 이미지를 로드하고, 그 이후에는 이미 로드된 이미지를 표시한다. 이렇게 Proxy 패턴을 사용하면 불필요한 이미지 로드를 줄이고 성능을 향상시킬 수 있다.

출처(참고문헌)

제가 학습한 내용을 요약하여 정리한 것입니다. 내용에 오류가 있을 수 있으며, 어떠한 피드백도 감사히 받겠습니다.

감사합니다

0개의 댓글