프록시 패턴
개요
갤러리의 이미지가 실제로 표시되기 전에는 무거운 로딩을 미루고 싶다고 하자. 호출부마다 로딩 여부를 검사하는 대신, 이미지처럼 사용할 수 있는 대리 객체를 둘 수 있다.
프록시(Proxy)는 실제 객체 대신 같은 인터페이스로 요청을 받으며 접근을 제어한다. 이 예제는 처음 그릴 때 실제 객체를 만드는 지연 생성 프록시다.
UML
RealImage와 ImageProxy는 IImage를 구현한다. ImageProxy는 Lazy<RealImage>를 보관하고 Draw()가 호출되면 실제 객체에 위임한다.
역사
GoF는 1994년 Design Patterns에서 Proxy를 구조 패턴으로 정리했다. 다른 객체에 대한 대리자나 자리표시자를 두어 접근을 제어하는 구조다.1
왜 사용하는가?
호출부는 실제 이미지와 프록시를 같은 방식으로 사용한다. 로딩을 미룰지 같은 접근 제어 선택은 프록시 한곳에서 다룬다.
권한을 검사하는 프록시라면 요청마다 현재 권한을 확인해야 한다. 여기서는 생성 지연만 다루며, 인증이나 원격 통신을 구현했다고 간주하지 않는다.
예제
프록시를 만든 시점과 처음 Draw()를 부른 뒤의 실제 객체 수를 비교한다. 파일을 여는 대신 생성 횟수만 센다.
using System;
using System.Collections.Generic;
using System.Linq;
IImage image = new ImageProxy();
Console.WriteLine(RealImage.Created);
Console.WriteLine(image.Draw());
Console.WriteLine(RealImage.Created);
interface IImage { string Draw(); }
sealed class RealImage : IImage
{
public static int Created { get; private set; }
public RealImage() => Created++;
public string Draw() => "draw image";
}
sealed class ImageProxy : IImage
{
private readonly Lazy<RealImage> image = new(() => new RealImage());
public string Draw() => image.Value.Draw();
}
0
draw image
1프록시 생성만으로 RealImage가 만들어지지는 않는다. Draw()가 Lazy.Value에 접근한 뒤 하나가 생성된다.
범위 넓힌 예제
같은 프록시로 두 번 그려도 실제 객체는 한 번만 생성된다.
using System;
using System.Collections.Generic;
using System.Linq;
IImage image = new ImageProxy();
Console.WriteLine(image.Draw());
Console.WriteLine(image.Draw());
Console.WriteLine("created: " + RealImage.Created);
interface IImage { string Draw(); }
sealed class RealImage : IImage
{
public static int Created { get; private set; }
public RealImage() => Created++;
public string Draw() => "draw image";
}
sealed class ImageProxy : IImage
{
private readonly Lazy<RealImage> image = new(() => new RealImage());
public string Draw() => image.Value.Draw();
}
draw image
draw image
created: 1Lazy가 재사용하는 것은 실제 객체다. 실제 객체의 Draw()는 매 호출마다 실행하므로 결과 데이터 캐시와는 구별된다. 실제 자원이 파일이나 GPU 객체라면 해제 책임도 정해야 한다.
같이 보기
각 예제는 독립된 .NET 10 콘솔 프로그램이다.