Давайте теперь рассмотрим, как можно применить один дизайн-паттерн с совершенно другой целью к системе сортировки отходов.
Мы больше не будем заботиться об оптимизации при добавлении нового типа Trash
в систему. На самом деле, этот паттерн делает добавление новых типов Trash
ещё более сложной задачей. Предположим, что у нас есть базовая структура классов, которая является незыблемой; она может происходить от другого разработчика или компании, и мы не имеем права делать какие-либо изменения в этой структуре. Однако нам всё же хочется добавить новые полиморфные методы в эту структуру. Это означает, что нам обычно придётся добавить что-то в интерфейс базового класса. Таким образом, наша текущая проблема заключается в необходимости добавления методов в базовый класс, но при этом нельзя менять сам базовый класс. Как решить эту проблему?
Паттерн "посетителя" (Visitor
) позволяет расширять интерфейсы базовых типов путём создания отдельной структуры классов типа Visitor
, который представляет собой виртуальные операции над базовыми типами. Базовым типам остаётся просто "принять" посетителей и вызвать динамически связанные методы посетителя. Это выглядит примерно так:
Теперь, если v — это ссылка на объект типа Aluminum
(алюминий) типа Visitable
, то следующий код:```java
PriceVisitor pv = new PriceVisitor();
v.accept(pv);
вызовет два полиморфных метода: первый выберет версию метода `accept()` для типа `Aluminum`; второй будет вызван внутри `accept()`, когда базовый класс `Visitor` использует ссылку `v` для динамического вызова конкретной версии метода `visit()`.
Эта конфигурация позволяет добавлять новые возможности в систему в виде новых подклассов `Visitor`, без необходимости изменения структуры `Trash`. Вот основное преимущество паттерна "посетителя": возможность добавления новых полиморфных функциональностей к структуре классов без необходимости её изменения — достаточно установить метод `accept()`. Обратите внимание, что это преимущество полезно здесь, но не обязательно является наилучшим выбором в любой ситуации. Поэтому важно определить заранее, действительно ли это подходящий вариант.
Теперь обратите внимание на то, что было сделано неверно: схема посетителя препятствует переходу от главного массива `Trash` к отдельному массиву типов. Так что мы можем оставить все объекты в одном главном массиве и использовать соответствующие посетители для прохождения через него, чтобы достичь желаемых целей. Хотя это может не быть первоначальной целью паттерна посетителя, он действительно помогает достичь желаемого результата (избежание использования RTTI).Примечание: RTTI — информация о типах во время выполнения (Run-Time Type Information). Паттерн посещаемости включает двойной распределитель, который одновременно проверяет типы `Trash` и `Visitor`. В следующем примере показаны две реализации `Visitor`: `PriceVisitor`, используемый для вычисления общего веса и стоимости, а также `WeightVisitor`, отслеживающий вес.
Все это реализовано с использованием нового, улучшенного версии программы по сбору мусора. Как и в случае с `DoubleDispatch.java`, класс `Trash` остаётся независимым, создавая новый интерфейс для добавления метода `accept()`:
```java
//: Visitable.java
// Интерфейс для добавления функциональности посетителя
// в иерархию классов `Trash` без изменения базового класса.
package c16.trashvisitor;
import c16.trash.*;
interface Visitable {
// Новый метод:
void accept(Visitor v);
} ///:~
Подклассы Aluminum
, Paper
, Glass
и Cardboard
реализуют метод accept()
:
//: VAluminum.java
// Aluminium для паттерна посетителей
package c16.trashvisitor;
import c16.trash.*;
public class VAluminum extends Aluminum
implements Visitable {
public VAluminum(double wt) { super(wt); }
public void accept(Visitor v) {
v.visit(this);
}
} ///:~
//: VPaper.java
// Paper для паттерна посетителей
package c16.trashvisitor;
import c16.trash.*;
public class VPaper extends Paper
implements Visitable {
public VPaper(double wt) { super(wt); }
public void accept(Visitor v) {
v.visit(this);
}
} ///:~
//: VGlass.java
// Glass для паттерна посетителей
package c16.trashvisitor;
import c16.trash.*;
public class VGlass extends Glass
implements Visitable {
public VGlass(double wt) { super(wt); }
public void accept(Visitor v) {
v.visit(this);
}
} ///:~
//: VCardboard.java
// Cardboard для паттерна посетителей
package c16.trashvisitor;
import c16.trash.*;```java
public class VCardboard extends Cardboard
implements Visitable {
public VCardboard(double wt) {
super(wt);
}
public void accept(Visitor v) {
v.visit(this);
}
}
Так как базовый класс Visitor
ничего конкретного не требует, его можно сделать интерфейсом:
//: Visitor.java
// Основной интерфейс для посетителей
package c16.trashvisitor;
import c16.trash.*;
``````java
interface Посетитель {
void посетить(Алюминий a);
void посетить(Папир p);
void посетить(Стекло g);
void посетить(Картон c);
}
//~
```Программа создаст определённые типы Посетитель
, а затем отправит их через список объектов типа `Trash`:
//: TrashVisitor.java
// Шаблон "посетителя"
package c16.trashvisitor;
import c16.trash.*;
import java.util.*;
// Конкретная группа алгоритмов, упакованная
// в каждом реализации Visitor:
class PriceVisitor implements Visitor {
private double alSum; // Алюминий
private double pSum; // Бумага
private double gSum; // Стекло
private double cSum; // Коробка
public void visit(VAluminum al) {
double v = al.weight() * al.value();
System.out.println(
"Значение алюминия = " + v);
alSum += v;
}
public void visit(VPaper p) {
double v = p.weight() * p.value();
System.out.println(
"Значение бумаги = " + v);
pSum += v;
}
public void visit(VGlass g) {
double v = g.weight() * g.value();
System.out.println(
"Значение стекла = " + v);
gSum += v;
}
public void visit(VCardboard c) {
double v = c.weight() * c.value();
System.out.println(
"Значение коробки = " + v);
cSum += v;
}
void total() {
System.out.println(
"Общее значение алюминия: $" + alSum + "\n" +
"Общее значение бумаги: $" + pSum + "\n" +
"Общее значение стекла: $" + gSum + "\n" +
"Общее значение коробки: $" + cSum);
}
}
class WeightVisitor implements Visitor {
private double alSum; // Алюминий
private double pSum; // Бумага
private double gSum; // Стекло
private double cSum; // Коробка
public void visit(VAluminum al) {
alSum += al.weight();
System.out.println("Вес алюминия = "
+ al.weight());
}
public void visit(VPaper p) {
pSum += p.weight();
System.out.println("Вес бумаги = "
+ p.weight());
}
public void visit(VGlass g) {
gSum += g.weight();
System.out.println("Вес стекла = "
+ g.weight());
}
public void visit(VCardboard c) {
cSum += c.weight();
System.out.println("Вес коробки = "
+ c.weight());
}
}
``````java
void total() {
System.out.println("Общий вес алюминия:"
+ alSum);
System.out.println("Общий вес бумаги:"
+ pSum);
System.out.println("Общий вес стекла:"
+ gSum);
System.out.println("Общий вес коробки:"
+ cSum);
}
}
``````markdown
Обратите внимание, что форма метода `main()` снова изменилась. Теперь в нём используется всего один контейнер для мусора (`Trash`). Два объекта типа `Visitor` получают доступ к каждому элементу последовательности, выполняя свои задачи. Каждый тип `Visitor` отслеживает собственные данные и вычисляет общую стоимость и вес.
```Кроме того, когда элементы извлекаются из последовательности, единственным необходимым преобразованием является приведение к типу `Trash`. Это позволяет избежать проверки типов во время выполнения, за исключением неизбежной конвертации. Если бы Java поддерживала параметризованные типы, даже эту операцию можно было бы избежать.
Сравните это с двойной диспетчеризацией, которую мы рассматривали ранее. Один способ отличается тем, что каждый подкласс создаёт только одну перегрузку метода, например, `add()`. В данном случае каждый перегруженный метод `visit()` должен быть переопределён в каждом подклассе `Visitor`.
(1) Более глубокое взаимодействие?
Ещё есть множество других кода, где между структурой `Trash` и структурой `Visitor` существует явное "взаимодействие" (coupling). Однако внутри каждого набора классов есть высокий уровень внутренней связности: каждый выполняет свою конкретную задачу (структура `Trash` описывает мусор или отходы, а структура `Visitor` описывает действия над этим мусором). Как пример хорошего дизайна паттернов, это хороший старт. Однако пока его преимущества становятся очевидными только при добавлении новых типов `Visitor`. При добавлении новых типов `Trash`, он может оказаться менее гибким.Низкий уровень взаимодействия между классами и высокий уровень внутренней связности являются важными целями проектирования. Однако, если не быть внимательным, это может помешать достижению более совершенного дизайна. На первый взгляд, некоторые классы неизбежно имеют некоторое "взаимодействие" друг с другом. Такое взаимодействие обычно происходит в парах и называется "парой" (couplet) — например, коллекция и итератор (enumeration). Пары `Trash-Visitor` также могут рассматриваться как такая пара.
```
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )