티스토리 뷰

반응형

안녕하세요 Pingu입니다.🐧

 

지난 글에서는 구조 패턴 Flyweight Pattern에 대해 알아봤는데요, 이번 글에서는 GoF의 마지막 구조 패턴인 Proxy Pattern(프록시)에 대해 알아보도록 하겠습니다.

프록시 패턴이란?

프록시 패턴은 다른 객체에 대한 접근을 제어할 수 있도록 surrogate, placeholder를 제공할 수 있는 구조 패턴입니다. 프록시가 원본 객체에 대한 접근을 제어하기 때문에 어떤 요청이 원본 객체에 전달되기 전이나 후에 작업을 수행할 수 있어요.

 

제가 이해한 대로 프록시 객체를 표현하자면 어떤 객체가 존재하지만, 해당 객체를 바로 사용하기엔 부담이 되는 경우에는 프록시 객체를 통해 처리를 하고 해당 객체가 반드시 필요한 시점에야 비로소 해당 객체를 생성하여 사용하겠다는 의미입니다.

  • Subject (Service Interface)
    • 서비스의 인터페이스를 정의합니다. 프록시는 서비스 객체로 사용 할 수 있도록 인터페이스를 준수해야합니다.
  • Real Subject (Service)
    • 서비스는 비즈니스 로직을 제공하는 클래스입니다.
  • Proxy
    • 프록시 객체에는 서비스 객체를 참조하는 필드가 있습니다.
    • Lazy initialization, logging, access control, caching 등과 같은 작업을 완료 한 뒤 클라이언트의 요청을 서비스 객체에 전달합니다.
    • 참조하는 서비스 객체와 동일한 인터페이스를 준수합니다.
    • 프록시에는 Remote Proxy, Virtual Proxy, Protection Proxy가 있으며 각각의 책임으로 구분합니다.
      • Remote Proxy : 요청을 처리하고 서비스 객체에 이를 전달하는 역할을 합니다.
      • Virtual Proxy : 서비스 객체에 대한 정보를 캐싱하여 접근을 연기합니다.
      • Protection Proxy : 특정 작업을 요청한 객체가 해당 작업을 수행할 권한을 가지고 있는지 확인합니다.
  • Client
    • 서비스 객체가 필요한 모든 코드를 프록시에 전달하기 위해 동일한 인터페이스를 사용하는 서비스와 프록시 객체와 함께 작동해야합니다.

프록시 패턴은 언제 쓰나요?

프록시 패턴을 사용하면 객체에 대한 접근을 제어할 수 있다고 했는데요, 이를 하는 이유는 어떤 객체가 실제로 사용될 때까지 생성 및 초기화를 위한 처리를 미루는 것입니다.

 

예를 들어 화면에 다양한 객체가 있는 앱을 실행할 때를 생각해보겠습니다. 화면은 스크롤할 수 있는데, 사진도 있고, 다양한 콘텐츠들이 있어서 이를 모두 로드한 뒤 앱을 실행하려면 시간이 너무 많이 걸리는 거죠. 실제로 화면에 보이는 콘텐츠는 몇 개 없을 텐데 말이에요. 즉 이럴 때 프록시 패턴을 사용하여 해당 콘텐츠가 실제 화면에 나타났을 때 해당 콘텐츠를 로드하라고 전달해주면 실제로 사용할 때 해당 객체를 생성하게 됩니다. 즉 바로 생성하는 것이 아닌 생성을 지연시키고 싶을 때 사용하는 것이 프록시 패턴입니다.

프록시 패턴의 결과

장점

  • 클라이언트가 알지 못하는 상태에서 서비스 객체를 제어할 수 있습니다.
  • 클라이언트가 서비스 객체의 생명 주기를 신경 쓰지 않을 때 이를 관리할 수 있습니다.
  • 프록시는 서비스 객체가 준비되지 않았거나 사용할 수 없는 경우에도 작동합니다.
  • 서비스나 클라이언트를 변경하지 않고 새로운 프록시를 도입할 수 있습니다.

단점

  • 새로운 클래스를 도입해야 하므로 코드가 복잡해집니다.
  • 응답이 지연될 수 있습니다.

예제

그럼 이제 Proxy Pattern을 Swift로 구현해보겠습니다.

유튜브 비디오 예제를 만들어 보겠습니다.

유튜브를 보면 영상 목록이 있고 마우스를 올리면 영상이 프리뷰 형태로 재생되는데요, 영상 목록을 구성할 때는 영상 정보가 없어도 됩니다.

하지만 프리뷰를 보여주기 위해서는 영상 정보가 존재해야겠죠.

즉 영상 목록만 보여줄 때는 Proxy 객체가 처리하고 마우스를 올려서 프리뷰를 보려고 할 때 영상 객체를 생성해서 보여주면 될 것 같습니다.

또한 영상 이름을 바꾸는 기능을 Proxy 객체가 클라이언트의 권한에 따라 제어하는 것도 한 번 만들어 볼게요.

 

일단 Subject(Service) 프로토콜을 먼저 만들어 볼게요.

// Subject (Service Interface)
protocol YoutubeService {
    func showPreview()
    func setName(name: String)
}

프리뷰를 보여주는 기능과 이름을 수정하는 기능을 구현하라고 정의했습니다.

그럼 실제 비디오 클래스를 만들어 보겠습니다.

// Real Service
class YoutubeVideo: YoutubeService {
    
    private var id: Int
    private var name: String
    
    init(id: Int, name: String) {
        self.id = id
        self.name = name
    }
    
    func showPreview() {
        print("\(name) Preview Play 😀\n")
    }
    
    func setName(name: String) {
        print("\(self.name)에서 \(name)으로 이름 수정 완료\n")
        self.name = name
    }
}

아까 정의한 프로토콜을 채택해서 간단하게 만들 수 있어요!

 

 

그럼 이제 Proxy 클래스를 만들어야 하는데, Proxy 클래스를 만들기 전에 Client를 먼저 간단하게 만들어보겠습니다.

// Client
class Client {
    var auth: AccessAuth
    
    init(auth: AccessAuth) {
        self.auth = auth
    }
}

enum AccessAuth {
    case owner
    case guest
}

간단하게 권한만 열거형으로 구분할 수 있는 Client 클래스입니다.

 

그럼 이젠 Proxy 클래스를 만들어 보겠습니다.

Proxy 클래스도 아까 정의한 프로토콜을 채택해야 합니다.

// Proxy
class YoutubeVideoProxy: YoutubeService {
    
    private var youtubeVideo: YoutubeService?
    
    private var id: Int
    private var name: String
    private var client: Client
    
    init(id: Int, name: String, client: Client) {
        self.id = id
        self.name = name
        self.client = client
    }
    
    func showTitle() {
        print("\(id), \(name) Youtube Video")
    }
    
    func showPreview() {
        if let youtubeVideo = self.youtubeVideo {
            youtubeVideo.showPreview()
        } else {
            loadVideoInfo()
            self.youtubeVideo?.showPreview()
        }
    }
    
    private func loadVideoInfo() {
        if let _ = self.youtubeVideo {
            print("Cache File Exist!\n")
        } else {
            print("load Video...🐧")
            self.youtubeVideo = YoutubeVideo(id: self.id, name: self.name)
        }
        
    }
    
    func setName(name: String) {
        if client.auth == .guest {
            print("Guest는 이름 수정 불가\n")
        } else if client.auth == .owner {
            self.loadVideoInfo()
            self.name = name
            self.youtubeVideo?.setName(name: name)
        }
    }
}

Proxy객체에서는 실제 비디오 객체가 없더라도 보여줄 수 있는 showTitle() 메서드와 비디오 객체가 필요할 때 로드할 수 있는 loadVideoInfo() 메서드가 추가적으로 구현되어있습니다.

 

비디오 객체가 필요 없을 때는 Proxy가 자체적으로 처리하다가 비디오 객체가 반드시 필요한 부분에서는 비디오 객체를 로드하고 이를 캐시에 저장하여 사용하는 방식으로 구현했습니다.

 

그럼 이렇게 만든 시스템을 실제로 사용해볼까요?

위의 코드를 보시면 지금은 owner 권한이라 이름도 바꿀 수 있었습니다.

showTitle()은 프록시 객체가 혼자서 처리할 수 있는 작업이라 비디오를 로드하지 않았지만, 

showPreview()와 setName(name:) 메서드는 비디오 객체가 필요합니다.

또한 showPreview()를 수행하며 비디오를 캐시에 저장했기 때문에 setName(name:)을 했을 땐 캐싱해서 사용한 것도 볼 수 있어요.

 

만약 위와 같이 guest 권한으로 이름을 변경하려고 하면 Proxy에서 막히는 것을 볼 수 있습니다.

 

 

이렇게 구조 패턴 중 하나인 Proxy Pattern에 대해 알아보고 간단하게 구현도 해봤습니다.

지금까지 공부한 패턴 중 가장 어려웠던 패턴이었습니다.

열심히 이해하려 했지만 제가 잘 이해한 건지도 잘 모르겠네요. 😥

혹시라도 틀린 부분이 있다면 알려주시면 감사하겠습니다.

 

전체 코드는 여기에서 볼 수 있습니다!

 

Proxy Pattern을 마지막으로 GoF 책에서 나오는 모든 구조 패턴을 공부했습니다.

다음 글부터는 GoF 책의 Behavior Pattern에 대해 알아보도록 하겠습니다.

 

감사합니다.

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함