Proxy (Заместитель)

Паттерн Proxy (Заместитель) предоставляет объект-заместитель, который контролирует доступ к другому объекту. Заместитель может перехватывать вызовы к реальному объекту и выполнять дополнительные действия — например, кеширование, логирование или проверку прав.

Когда применять Proxy

  • Когда необходимо управлять доступом к объекту, особенно если его создание или выполнение операций — ресурсоёмкое.
  • Когда нужно добавить дополнительное поведение (например, логирование или защиту) без изменения существующего класса.
  • Когда требуется лениво загружать объект (отложенная инициализация).

PlantUML-диаграмма

UML диаграмма Proxy

// Общий интерфейс
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 и системах безопасности.