Вступление
Шаблон проектирования прокси - это шаблон проектирования, принадлежащий к набору структурных шаблонов . Структурные шаблоны - это категория шаблонов проектирования, используемых для упрощения разработки программы на ее структурном уровне.
Как следует из названия, шаблон прокси означает использование прокси для некоторой другой сущности. Другими словами, прокси-сервер используется в качестве посредника перед существующим объектом или окружает его. Это можно использовать, например, когда фактический объект очень ресурсоемкий или когда есть определенные условия, которые необходимо проверить перед использованием фактического объекта. Прокси-сервер также может быть полезен, если мы хотим ограничить доступ или функциональность объекта.
В этой статье мы опишем шаблон прокси и покажем несколько примеров, в которых он может быть использован.
Идея прокси
Прокси-сервер используется для инкапсуляции функций другого объекта или системы. Рассмотрим , например, удаленный вызов метода , который является способом вызова методов на другой машине. В Java это достигается через удаленный прокси-сервер, который по сути является объектом, обеспечивающим локальное представление другого удаленного объекта. Вызов метода с другой машины становится возможным просто путем вызова метода прокси-объекта.
Каждый прокси реализован таким образом, что предлагает клиенту точно такой же интерфейс, что и реальный объект. Это означает, что клиент фактически не замечает никакой разницы при использовании прокси-объекта.
Есть несколько типов прокси-объектов. Как можно предположить из предыдущего примера, удаленные прокси используются для доступа к некоторым удаленным объектам или ресурсам. Помимо удаленных прокси, существуют также виртуальные прокси и прокси защиты . Кратко опишем каждый из них для лучшего понимания.
Удаленные прокси
Удаленные прокси-серверы предоставляют локальное представление другого удаленного объекта или ресурса. Удаленные прокси-серверы несут ответственность не только за представление, но и за некоторые работы по обслуживанию. Такая работа может включать подключение к удаленному компьютеру и поддержание соединения, кодирование и декодирование символов, полученных через сетевой трафик, синтаксический анализ и т. Д.
Виртуальные прокси
Виртуальные прокси-серверы обертывают дорогие объекты и загружают их по запросу. Иногда нам не нужны сразу все функции, которые предлагает объект, особенно если это требует много памяти / времени. Вызов объектов только при необходимости может немного повысить производительность, как мы увидим в примере ниже.
Прокси-серверы защиты
Прокси-серверы защиты используются для проверки определенных условий. Некоторым объектам или ресурсам может потребоваться соответствующая авторизация для доступа к ним, поэтому использование прокси - один из способов проверки таких условий. С защитными прокси-серверами мы также получаем гибкость в использовании множества вариантов контроля доступа.
Например, если мы пытаемся предоставить доступ к ресурсу операционной системы, обычно есть несколько категорий пользователей. У нас может быть пользователь, которому не разрешено просматривать или редактировать ресурс, пользователь, который может делать с ресурсом все, что пожелает, и т. Д.
Использование прокси-серверов в качестве оболочки для таких ресурсов - отличный способ реализовать индивидуальный контроль доступа.
Выполнение
Пример виртуального прокси
Одним из примеров виртуального прокси является загрузка изображений. Представим, что мы создаем файловый менеджер. Как и любой другой файловый менеджер, этот должен иметь возможность отображать изображения в папке, которую пользователь решает открыть.
Если мы предполагаем, что существует класс ImageViewer
, отвечающий за
загрузку и отображение изображений, мы могли бы реализовать наш файловый
менеджер, используя этот класс напрямую. Такой подход кажется логичным и
прямым, но в нем есть тонкая проблема.
Если мы реализуем файловый менеджер, как описано выше, мы будем загружать изображения каждый раз, когда они появляются в папке. Если пользователь хочет видеть только имя или размер изображения, такой подход все равно загрузит все изображение в память. Поскольку загрузка и отображение изображений - дорогостоящие операции, это может вызвать проблемы с производительностью.
Лучшим решением было бы отображать изображения только тогда, когда это
действительно необходимо . В этом смысле мы можем использовать прокси
для обертывания существующего объекта ImageViewer
Таким образом,
фактическое средство просмотра изображений будет вызываться только
тогда, когда изображение необходимо отрендерить. Все другие операции
(такие как получение имени изображения, размера, даты создания и т. Д.)
Не требуют фактического изображения и, следовательно, могут быть
получены с помощью гораздо более легкого прокси-объекта.
Давайте сначала создадим наш основной интерфейс:
interface ImageViewer {
public void displayImage();
}
Далее мы реализуем конкретную программу просмотра изображений. Обратите внимание, что операции, выполняемые в этом классе, являются дорогостоящими:
public class ConcreteImageViewer implements ImageViewer {
private Image image;
public ConcreteImageViewer(String path) {
// Costly operation
this.image = Image.load(path);
}
@Override
public void displayImage() {
// Costly operation
image.display();
}
}
Теперь мы реализуем наш облегченный прокси для просмотра изображений.
Этот объект будет вызывать конкретную программу просмотра изображений
только при необходимости, то есть когда клиент вызывает метод
displayImage()
. До тех пор изображения не будут загружаться или
обрабатываться , что сделает нашу программу намного более эффективной.
public class ImageViewerProxy implements ImageViewer {
private String path;
private ImageViewer viewer;
public ImageViewerProxy(String path) {
this.path = path;
}
@Override
public void displayImage() {
this.viewer = new ConcreteImageViewer(this.path);
this.viewer.displayImage();
}
}
Наконец, мы напишем клиентскую часть нашей программы. В приведенном ниже коде мы создаем шесть разных программ просмотра изображений. Во-первых, три из них являются конкретными программами просмотра изображений, которые автоматически загружают изображения при создании. Последние три изображения не загружают изображения в память при создании.
Только с последней строки первая программа просмотра прокси начнет загрузку изображения. По сравнению с конкретными зрителями преимущества в производительности очевидны:
public static void main(String[] args) {
ImageViewer flowers = new ConcreteImageViewer("./photos/flowers.png");
ImageViewer trees = new ConcreteImageViewer("./photos/trees.png");
ImageViewer grass = new ConcreteImageViewer("./photos/grass.png");
ImageViewer sky = new ImageViewerProxy("./photos/sky.png");
ImageViewer sun = new ImageViewerProxy("./photos/sun.png");
ImageViewer clouds = new ImageViewerProxy("./photos/clouds.png");
sky.displayImage();
}
Еще мы могли бы добавить null
проверку в метод displayImage()
ImageViewerProxy
:
@Override
public void displayImage() {
if (this.viewer == null) {
this.viewer = new ConcreteImageViewer(this.path);
}
this.viewer.displayImage();
}
Итак, если мы позвоним:
ImageViewer sky = new ImageViewerProxy("./photos/sky.png");
sky.displayImage();
sky.displayImage();
Только один раз будет выполнен new ConcreteImageViewer
вызов
ConcreteImageViewer. Это еще больше уменьшит объем памяти, занимаемый
нашим приложением.
Примечание. Этот пример не содержит полностью компилируемого кода Java. Некоторые вызовы методов, такие как
Image.load(String path)
, являются вымышленными и написаны в упрощенном виде в основном для иллюстративных целей.
Пример защиты прокси
В этом примере мы будем летать на космическом корабле. Перед этим нам
нужно создать две вещи: интерфейс Spaceship
и модель Pilot
interface Spaceship {
public void fly();
}
public class Pilot {
private String name;
// Constructor, Getters, and Setters
}
Теперь мы собираемся реализовать Spaceship
и создать настоящий класс
космического корабля:
public class MillenniumFalcon implements Spaceship {
@Override
public void fly() {
System.out.println("Welcome, Han. The Millennium Falcon is starting up its engines!");
}
}
Класс MillenniumFalcon
представляет собой конкретный космический
корабль, который может использоваться нашим Pilot
. Однако могут быть
некоторые условия, которые мы могли бы проверить, прежде чем позволить
пилоту управлять космическим кораблем. Например, возможно, мы хотели бы
узнать, есть ли у пилота соответствующий сертификат или он достаточно
взрослый, чтобы летать. Чтобы проверить эти условия, мы можем
использовать шаблон проектирования прокси.
В этом примере мы собираемся проверить, зовут ли пилота «Хан Соло»,
поскольку он является законным владельцем корабля. Начнем с реализации
Spaceship
как и раньше.
Мы собираемся использовать Pilot
и Spaceship
качестве переменных
нашего класса, поскольку мы можем получить от них всю необходимую
информацию:
public class MillenniumFalconProxy implements Spaceship {
private Pilot pilot;
private Spaceship falcon;
public MillenniumFalconProxy(Pilot pilot) {
this.pilot = pilot;
this.falcon = new MillenniumFalcon();
}
@Override
public void fly() {
if (pilot.getName().equals("Han Solo")) {
falcon.fly();
} else {
System.out.printf("Sorry %s, only Han Solo can fly the Falcon!\n", pilotName);
}
}
}
Затем можно написать клиентскую часть программы, как показано ниже. Если «Хан Соло» будет пилотом, то «Соколу» будет разрешено летать. В противном случае из ангара не разрешат:
public static void main(String[] args) {
Spaceship falcon1 = new MillenniumFalconProxy(new Pilot("Han Solo"));
falcon1.fly();
Spaceship falcon2 = new MillenniumFalconProxy(new Pilot("Jabba the Hutt"));
falcon2.fly();
}
Результатом для вышеуказанных вызовов будет следующее:
Welcome, Han. The Millennium Falcon is starting up its engines!
Sorry Jabba the Hutt, only Han Solo can fly the Falcon!
Плюсы и минусы
Плюсы
- Безопасность : с помощью прокси можно проверить определенные условия при доступе к объекту и обеспечить контролируемое использование потенциально «опасных» классов и ресурсов.
- Производительность : некоторые объекты могут быть очень требовательны к памяти и времени выполнения. Используя прокси, мы можем обернуть такие объекты дорогостоящими операциями, чтобы они вызывались только тогда, когда это действительно необходимо, или избежать ненужного создания экземпляров.
Минусы
-
Производительность : Да, производительность также может быть недостатком шаблона прокси. Как, спросите вы? Предположим, что прокси-объект используется для обертывания объекта, существующего где-то в сети. Поскольку это прокси, он может скрыть от клиента тот факт, что задействован удаленный обмен данными.
Это, в свою очередь, может склонить клиента к написанию неэффективного кода, поскольку он не будет знать, что в фоновом режиме выполняется дорогостоящий сетевой вызов.
Заключение
Шаблон проектирования прокси - это умный способ использования некоторых дорогостоящих ресурсов или предоставления определенных прав доступа. Он структурно похож на шаблоны Adapter и Decorator , но имеет другое назначение.
Прокси-сервер можно использовать в самых разных обстоятельствах, поскольку в программировании часто возникают требующиеся ресурсы, особенно при работе с базами данных и сетями.
Поэтому знание того, как эффективно получить доступ к этим ресурсам при обеспечении надлежащего контроля доступа, имеет решающее значение для создания масштабируемых и безопасных приложений.