1. 프록시 패턴이란?
- 어떤 객체를 사용하고자 할 때, 객체를 직접적으로 참조 하는것이 아니라, 해당 객체를 대행(대리, proxy)하는 객체를 통해 대상 객체에 접근하는 방식을 사용하는 패턴.
- 해당 객체가 메모리에 존재하지 않아도 기본적인 정보를 참조하거나 설정할 수 있고, 실제 객체의 기능이 반드시 필요한 시점까지 객체의 생성을 미룰 수 있다.
- 원래 객체에 대한 접근을 제어할 수 있어, 요청이 들어와 원래 객체에 전달되기 전이나 후에 무언가를 수행할 수 있다.
- 대리자로써 작업 수행을 요청하면 처리 후 그 결과를 알려주는 패턴.
- 특정 객체에 대한 접근을 제어하거나 기능을 추가할 수 있는 패턴.
- 상속받은 인터페이스의 클래스 내부에서 같은 인터페이스를 상속받은 인스턴스를 생성하는 패턴.
2. 프록시 패턴을 사용하는 경우
- 원본 객체를 수정할 수 없는 상황을 극복하기 위해 사용
cf) 프록시 종류
1) 가상 프록시
- 꼭 필요로 하는 시점까지 객체의 생성을 연기하고, 해당 객체가 생성된 것처럼 동작하도록 만들고 싶을 때 사용하는 패턴.
- 프록시 클래스에서 자잘한 작업들을 처리하고, 리소스가 많이 요구되는 작업들이 필요할 때만 주체 클래스를 사용하도록 구현한다.
- ex) 해상도가 높은 이미지를 텍스트와 분산하여 처리하는 과정
2) 원격 프록시
- 원격 객체에 대한 제어가 로컬 환경에 존재하며, 원격 객체에 대한 프록시 객체가 서로 다른 주소 공간에 있는 객체에 대해 마치 같은 주소 공간에 있는 것처럼 동작하게 만드는 패턴.
- ex) Google Docs
3) 보호 프록시
- 주체 클래스에 대한 접근을 제어하기 위한 경우, 객체에 대한 접근 권한을 제어하거나 객체마다 접근 권한을 달리하고 싶을 때 사용하는 패턴
4) 로깅 프록시
- 대상 객체에 대한 로깅을 추가하려는 경우
- 프록시는 서비스 메서드를 실행하고 전달하기 전에, 로깅을 하는 기능을 추가하여 재정의한다.
5) 캐싱 프록시
- 데이터가 큰 경우 캐싱하여 재사용을 유도
- 클라이언트 요청의 결과를 캐시하고, 이 캐시의 수명 주기를 관리
3. 프록시 패턴의 장점
1) 사이즈가 큰 객체가 로딩되기 전에 프록시를 통해 참조할 수 있다.
2) 실체 객체의 public, protected 메소드들을 숨기고 인터페이스를 통해 노출시킬 수 있다.
3) 로컬에 있지 않고 떨어져 있는 객체를 사용할 수 있다.
4) 원래 객체의 접근에 대해서 사전처리를 할 수 있다.
4. 프록시 패턴의 단점
1) 객체를 생성할 때 한 단계를 더 거치게 되므로, 빈번한 객체 생성이 필요한 경우 성능이 저하될 수 있다.
2) 프록시 내부에서 객체 생성을 위해 스레드가 생성, 동기화가 구현되야 하는 경우 성능이 저하될 수 있다.
3) 로직이 난해해져 가독성이 떨어질 수 있다.
5. 코드 구현
1) 기본형 프록시
using System;
using System.Collections.Generic;
namespace StudyCSharp
{
interface ISubject
{
void action();
}
class RealSubject : ISubject
{
public void action()
{
Console.WriteLine("원본 객체");
}
}
// 기본형 프록시
class Proxy : ISubject
{
private RealSubject _subject; // 대상 객체
public Proxy(RealSubject subject)
{
this._subject = subject;
}
public void action()
{
_subject.action(); // 위임 -> RealSubject의 함수를 대신하여 호출
Console.WriteLine("프록시 객체");
}
}
class Program
{
static void Main(string[] args)
{
ISubject sub = new Proxy(new RealSubject());
sub.action();
}
}
}
- 실행 결과
2) 가상 프록시
using System;
using System.Collections.Generic;
namespace StudyCSharp
{
interface ISubject
{
void action();
}
class RealSubject : ISubject
{
public void action()
{
Console.WriteLine("원본 객체");
}
}
// 가상 프록시
class Proxy : ISubject
{
private RealSubject _subject; // 대상 객체
public Proxy() { }
public void action()
{
if(_subject == null)
{
this._subject = new RealSubject();
}
_subject.action(); // 위임 -> RealSubject의 함수를 대신하여 호출
Console.WriteLine("가상 프록시 객체");
}
}
class Program
{
static void Main(string[] args)
{
ISubject sub = new Proxy();
sub.action();
}
}
}
- 실행 결과
3) 보호 프록시
using System;
using System.Collections.Generic;
namespace StudyCSharp
{
interface ISubject
{
void action();
}
class RealSubject : ISubject
{
public void action()
{
Console.WriteLine("원본 객체");
}
}
// 보호 프록시
class Proxy : ISubject
{
private RealSubject _subject; // 대상 객체
private bool _access;
public Proxy(RealSubject subject, bool access)
{
this._subject = subject;
this._access = access;
}
public void action()
{
if(_access )
{
_subject.action(); // 위임 -> RealSubject의 함수를 대신하여 호출
}
Console.WriteLine("프록시 객체");
}
}
class Program
{
static void Main(string[] args)
{
ISubject sub = new Proxy(new RealSubject(), true);
sub.action();
Console.WriteLine("---------------------------------");
ISubject sub2 = new Proxy(new RealSubject(), false);
sub2.action();
}
}
}
- 실행 결과
4) 로깅 프록시
using System;
using System.Collections.Generic;
namespace StudyCSharp
{
interface ISubject
{
void action();
}
class RealSubject : ISubject
{
public void action()
{
Console.WriteLine("원본 객체");
}
}
// 로깅 프록시
class Proxy : ISubject
{
private RealSubject _subject; // 대상 객체
public Proxy(RealSubject subject)
{
this._subject = subject;
}
public void action()
{
Console.WriteLine("로깅...............");
_subject.action(); // 위임 -> RealSubject의 함수를 대신하여 호출
Console.WriteLine("프록시 객체");
Console.WriteLine("로깅...............");
}
}
class Program
{
static void Main(string[] args)
{
ISubject sub = new Proxy(new RealSubject());
sub.action();
}
}
}
- 실행결과
5) 가상 프록시 적용 전 후 비교
- 적용 전
using System;
using System.Collections.Generic;
using System.Threading;
namespace StudyCSharp
{
class HighResolutionImage
{
string img;
public HighResolutionImage(string path)
{
loadImage(path);
}
private void loadImage(string path)
{
// 이미지를 디스크에서 불러와 메모리에 적재(작업 자체가 무겁고 많은 자원을 필요로 함)
try
{
Thread.Sleep(1000);
img = path;
}
catch (ThreadInterruptedException e)
{
// 에러의 경로를 찾음
Console.WriteLine(e.StackTrace.ToUpper());
}
Console.WriteLine("{0}에 있는 이미지 로딩 완료", path);
}
public void showImage()
{
// 이미지를 화면에 렌더링
Console.WriteLine("{0} 이미지 출력", img);
}
}
class Program
{
static void Main(string[] args)
{
HighResolutionImage highResolutionImage1 = new HighResolutionImage("./img/고해상도 이미지_1");
HighResolutionImage highResolutionImage2 = new HighResolutionImage("./img/고해상도 이미지_2");
HighResolutionImage highResolutionImage3 = new HighResolutionImage("./img/고해상도 이미지_3");
highResolutionImage2.showImage();
}
}
}
- 실행 결과
: 이미지여서 볼 순 없지만, 실행해보면 약간의 텀을 두고 이미지 1,2,3이 차례로 로딩된 후 이미지 2가 출력되는 것을 알 수 있다.
- 적용 후
using System;
using System.Collections.Generic;
using System.Threading;
namespace StudyCSharp
{
interface IImage
{
void showImage();
}
class HighResolutionImage : IImage
{
string img;
public HighResolutionImage(string path)
{
loadImage(path);
}
private void loadImage(string path)
{
// 이미지를 디스크에서 불러와 메모리에 적재(작업 자체가 무겁고 많은 자원을 필요로 함)
try
{
Thread.Sleep(1000);
img = path;
}
catch(ThreadInterruptedException e)
{
// 에러의 경로를 찾음
Console.WriteLine(e.StackTrace.ToUpper());
}
Console.WriteLine("{0}에 있는 이미지 로딩 완료", path);
}
// Override
public void showImage()
{
// 이미지를 화면에 렌더링
Console.WriteLine("{0} 이미지 출력", img);
}
}
class ImageProxy : IImage
{
private IImage proxyImage;
private string _path;
public ImageProxy(string path)
{
this._path = path;
}
// Override
public void showImage()
{
// 고해상도 이미지 로딩
proxyImage = new HighResolutionImage(this._path);
proxyImage.showImage();
}
}
class Program
{
static void Main(string[] args)
{
IImage highResolutionImage1 = new ImageProxy("./img/고해상도 이미지_1");
IImage highResolutionImage2 = new ImageProxy("./img/고해상도 이미지_2");
IImage highResolutionImage3 = new ImageProxy("./img/고해상도 이미지_3");
highResolutionImage2.showImage();
}
}
}
- 실행 결과
: 이미지여서 볼 순 없지만, 실행 후 약간의 시간이 지난 뒤 두 출력 메세지가 동시에 뜬다.
<참고 사이트>