프록시는 너로 하여금 대리자나 또 다른 객체를 위한 placeholder를 제공하도록 하는 구조적인 디자인 패턴이다. 프록시는 너로 하여금 요청이 오리지널 객체에 전달되기 전이나 후로 무언가를 수행가하면서 오리지널 객체에 대한 접근을 제어한다.
왜 객체에 대한 접근을 제어하기를 원하는가? 여기 한 예가 있다: 막대한 양의 시스템 자원을 소비하는 거대한 객체가 있다. 너는 그것이 때때로 필요하지만 항상은 아니다.
Database queries can be really slow.
너는 지연 초기화(처음 필요할 때까지 객체 생성, 값 계산 또는 기타 값비싼 프로세스를 지연시키는 전술)를 구현할 수 있다: 실제로 필요할 때만 객체를 생성한다. 모든 객체의 클라이언트는 몇몇 지연된 초기화 코드를 실행해야 한다. 불행히도, 이것은 아마도 많은 코드의 중복을 야기할 것이다.
이상적인 세계에서, 우리는 이 코드를 직접 우리의 객체들의 클래스 안에 놓기를 원하지만, 항상 가능한 것만은 아니다. 예를 들어, 해당 클래스가 폐쇄된 3rd-party 라이브러리의 일부일지도 모른다.
프록시 패턴은 원래 서비스 객체와 동일한 인터페이스로 새 프록시 클래스를 생성하도록 제안한다. 그 다음 너는 너의 앱이 원래 객체들의 모든 클라이언트에 프록시 객체를 통과하기 위해서 너의 앱을 업데이트한다. 클라이언트로부터 요청을 받자마자, 프록시는 진짜 서비스 객체를 생성하고 모든 작업을 위임한다.
The proxy disguises itself as a database object. It can handle lazy initialization and result caching without the client or the real database object even knowing.
하지만 이익이 뭔가? 만약 주된 클래스 로직 전과 후에 무언가를 실행할 필요가 있다면, 프록시는 해당 클래스를 변경하지 않고 이것을 수행하도록 해준다. 프록시는 원본 클래스로서 같은 인터페이스를 수행하기 때문에, 진짜 서비스 객체를 기대하는 어떠한 클라이언트에 통과될 수 있다.
Credit cards can be used for payments just the same as cash.
CreditCard는 Cash 묶음을 위한 프록시인 은행 계좌에 대한 프록시이다. 둘다 같은 인터페이스를 구현한다: 그들은 Payment를 만드는 것에 대해 사용될 수 있다. 소비자는 많은 현금을 가지고 다닐 필요가 없기 때문에 기분이 좋다. 가게 주인은 예금을 읽어버리거나 은행으로 가는 길에 강도를 당하는 위험이 없이 가게의 은행 계좌로 전자적으로 거래로부터 오는 수입이 더해지기 때문에 기분이 좋다.
The Service Interface declares the interface of the Service. The proxy must follow this interface to be able to disguise itself as a service object.
The Service is a class that provides some useful business logic.
The Proxy class has a reference field that points to a service object. After the proxy finishes its processing (e.g., lazy initialization, logging, access control, caching, etc.), it passes the request to the service object.
Usually, proxies manage the full lifecycle of their service objects.
The Client should work with both services and proxies via the same interface. This way you can pass a proxy into any code that expects a service object.
이 예제는 어떻게 프록시 패턴이 지연 초기화와 3rd-party 유튜브 통합 라이브러리를 캐싱하는지 소개하는 것을 도와줄 수 있다.
Caching results of a service with a proxy.
라이브러리는 우리에게 비디오 다운로딩 클래스를 함께 제공해준다. 그러나, 그것은 매우 비효율적이다. 만약 클라이언트 애플리케이션이 같은 비디오를 여러 차례 요청한다면, 해당 라이브러리는 캐싱하고 첫 번째로 다운로드된 파일을 다시 사용하는 것 대신에 단지 계속해서 다운로드한다.
프록시 클래스는 원래의 다운로더로써 같은 인터페이스를 구현하고 모든 작업을 위임한다. 그러나, 앱이 동일한 비디오를 여러 차례 요청할 때 다운로드한 파일을 추적하고 캐시된 결과를 반환한다.
// The interface of a remote service.
interface ThirdPartyYouTubeLib is
method listVideos()
method getVideoInfo(id)
method downloadVideo(id)
// The concrete implementation of a service connector. Methods
// of this class can request information from YouTube. The speed
// of the request depends on a user's internet connection as
// well as YouTube's. The application will slow down if a lot of
// requests are fired at the same time, even if they all request
// the same information.
class ThirdPartyYouTubeClass implements ThirdPartyYouTubeLib is
method listVideos() is
// Send an API request to YouTube.
method getVideoInfo(id) is
// Get metadata about some video.
method downloadVideo(id) is
// Download a video file from YouTube.
// To save some bandwidth, we can cache request results and keep
// them for some time. But it may be impossible to put such code
// directly into the service class. For example, it could have
// been provided as part of a third party library and/or defined
// as `final`. That's why we put the caching code into a new
// proxy class which implements the same interface as the
// service class. It delegates to the service object only when
// the real requests have to be sent.
class CachedYouTubeClass implements ThirdPartyYouTubeLib is
private field service: ThirdPartyYouTubeLib
private field listCache, videoCache
field needReset
constructor CachedYouTubeClass(service: ThirdPartyYouTubeLib) is
this.service = service
method listVideos() is
if (listCache == null || needReset)
listCache = service.listVideos()
return listCache
method getVideoInfo(id) is
if (videoCache == null || needReset)
videoCache = service.getVideoInfo(id)
return videoCache
method downloadVideo(id) is
if (!downloadExists(id) || needReset)
service.downloadVideo(id)
// The GUI class, which used to work directly with a service
// object, stays unchanged as long as it works with the service
// object through an interface. We can safely pass a proxy
// object instead of a real service object since they both
// implement the same interface.
class YouTubeManager is
protected field service: ThirdPartyYouTubeLib
constructor YouTubeManager(service: ThirdPartyYouTubeLib) is
this.service = service
method renderVideoPage(id) is
info = service.getVideoInfo(id)
// Render the video page.
method renderListPanel() is
list = service.listVideos()
// Render the list of video thumbnails.
method reactOnUserInput() is
renderVideoPage()
renderListPanel()
// The application can configure proxies on the fly.
class Application is
method init() is
aYouTubeService = new ThirdPartyYouTubeClass()
aYouTubeProxy = new CachedYouTubeClass(aYouTubeService)
manager = new YouTubeManager(aYouTubeProxy)
manager.reactOnUserInput()
프록시 패턴을 사용하기 위한 수십개의 방법이 있습니다.
(pros)
(cons)