1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/wizardforcel-thinking-in-java-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
16.7 访问器模式.md 14 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 11.03.2025 09:15 d56454c

16.7 Посетитель паттерн

Давайте теперь рассмотрим, как можно применить один дизайн-паттерн с совершенно другой целью к системе сортировки отходов.

Мы больше не будем заботиться об оптимизации при добавлении нового типа 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 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/wizardforcel-thinking-in-java-zh.git
git@api.gitlife.ru:oschina-mirror/wizardforcel-thinking-in-java-zh.git
oschina-mirror
wizardforcel-thinking-in-java-zh
wizardforcel-thinking-in-java-zh
master