Паттерн Proxy (Заместитель) предоставляет объект-заместитель, который контролирует доступ к другому объекту. Заместитель может перехватывать вызовы к реальному объекту и выполнять дополнительные действия — например, кеширование, логирование или проверку прав.
Когда применять Proxy
- Когда необходимо управлять доступом к объекту, особенно если его создание или выполнение операций — ресурсоёмкое.
- Когда нужно добавить дополнительное поведение (например, логирование или защиту) без изменения существующего класса.
- Когда требуется лениво загружать объект (отложенная инициализация).
PlantUML-диаграмма
// Общий интерфейс
public interface IImage
{
void Display();
}
// Реальный объект
public class RealImage : IImage
{
private string _fileName;
public RealImage(string fileName)
{
_fileName = fileName;
LoadFromDisk();
}
private void LoadFromDisk()
{
Console.WriteLine($"Загрузка изображения с диска: {_fileName}");
}
public void Display()
{
Console.WriteLine($"Отображение изображения: {_fileName}");
}
}
// Заместитель
public class ProxyImage : IImage
{
private string _fileName;
private RealImage _realImage;
public ProxyImage(string fileName)
{
_fileName = fileName;
}
public void Display()
{
if (_realImage == null)
{
_realImage = new RealImage(_fileName);
}
else
{
Console.WriteLine("Используем кешированное изображение");
}
_realImage.Display();
}
}
// Клиентский код
class Program
{
static void Main()
{
IImage image = new ProxyImage("photo.png");
// Первое обращение — загружает изображение
image.Display();
// Второе обращение — использует кеш
image.Display();
}
}
// Общий интерфейс
type Image interface {
Display()
}
// Реальный объект
type RealImage struct {
fileName string
}
func NewRealImage(fileName string) *RealImage {
img := &RealImage{fileName: fileName}
img.loadFromDisk()
return img
}
func (i *RealImage) loadFromDisk() {
fmt.Printf("Загрузка изображения с диска: %s\n", i.fileName)
}
func (i *RealImage) Display() {
fmt.Printf("Отображение изображения: %s\n", i.fileName)
}
// Заместитель: создаёт реальный объект только при первом обращении
type ProxyImage struct {
fileName string
real *RealImage
}
func (p *ProxyImage) Display() {
if p.real == nil {
p.real = NewRealImage(p.fileName)
} else {
fmt.Println("Используем кешированное изображение")
}
p.real.Display()
}
func main() {
var image Image = &ProxyImage{fileName: "photo.png"}
image.Display()
image.Display()
}
from typing import Protocol
class Image(Protocol):
def display(self) -> None: ...
class RealImage:
def __init__(self, file_name: str) -> None:
self._file_name = file_name
self._load_from_disk()
def _load_from_disk(self) -> None:
print(f"Загрузка изображения с диска: {self._file_name}")
def display(self) -> None:
print(f"Отображение изображения: {self._file_name}")
class ProxyImage:
def __init__(self, file_name: str) -> None:
self._file_name = file_name
self._real: RealImage | None = None
def display(self) -> None:
if self._real is None:
self._real = RealImage(self._file_name)
else:
print("Используем кешированное изображение")
self._real.display()
image: Image = ProxyImage("photo.png")
image.display()
image.display()
interface Image {
display(): void;
}
class RealImage implements Image {
constructor(private readonly fileName: string) {
this.loadFromDisk();
}
private loadFromDisk(): void {
console.log(`Загрузка изображения с диска: ${this.fileName}`);
}
display(): void {
console.log(`Отображение изображения: ${this.fileName}`);
}
}
class ProxyImage implements Image {
private real: RealImage | null = null;
constructor(private readonly fileName: string) {}
display(): void {
if (this.real === null) {
this.real = new RealImage(this.fileName);
} else {
console.log("Используем кешированное изображение");
}
this.real.display();
}
}
const image: Image = new ProxyImage("photo.png");
image.display();
image.display();
interface Image {
void display();
}
class RealImage implements Image {
private final String fileName;
RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Загрузка изображения с диска: " + fileName);
}
public void display() {
System.out.println("Отображение изображения: " + fileName);
}
}
class ProxyImage implements Image {
private final String fileName;
private RealImage real;
ProxyImage(String fileName) {
this.fileName = fileName;
}
public void display() {
if (real == null) {
real = new RealImage(fileName);
} else {
System.out.println("Используем кешированное изображение");
}
real.display();
}
}
public class Program {
public static void main(String[] args) {
Image image = new ProxyImage("photo.png");
image.display();
image.display();
}
}
interface Image {
fun display()
}
class RealImage(private val fileName: String) : Image {
init {
println("Загрузка изображения с диска: $fileName")
}
override fun display() = println("Отображение изображения: $fileName")
}
class ProxyImage(private val fileName: String) : Image {
private var real: RealImage? = null
override fun display() {
val cached = real
val image = if (cached == null) {
RealImage(fileName).also { real = it }
} else {
println("Используем кешированное изображение")
cached
}
image.display()
}
}
fun main() {
val image: Image = ProxyImage("photo.png")
image.display()
image.display()
}
Виды Proxy
- Virtual Proxy — откладывает создание ресурсоёмких объектов (ленивая инициализация).
- Protection Proxy — контролирует доступ к объекту (например, проверяет права пользователя).
- Remote Proxy — управляет взаимодействием с объектами, находящимися в другой среде (например, на сервере).
- Caching Proxy — сохраняет результаты работы объекта для ускорения повторных вызовов.
Плюсы и минусы
| Плюсы | Минусы |
|---|---|
| Позволяет контролировать доступ к объекту | Добавляет дополнительный уровень абстракции |
| Может улучшить производительность за счёт кеширования | Усложняет отладку и архитектуру системы |
| Поддерживает принцип единственной ответственности | Может скрывать побочные эффекты (например, задержки при ленивой загрузке) |
Заключение
Proxy — универсальный структурный паттерн, который позволяет добавить контроль доступа, оптимизацию или безопасность без изменения кода реального объекта. Он широко используется в сетевых сервисах, кешировании, ORM и системах безопасности.