Визитер (Visitor): вариант множественной диспетчеризации


Исходим из того, что главная иерархия класса, которая у вас есть, фиксирована; вероятно вы получили ее от стороннего производителя и вы не можете менять эту иерархию. Однако, вам нужно добавить новые полиморфические методы в эту иерархию, что обычно означает, что вы должны добавить что-то в интерфейс базового класса. Так что дилемма состоит в том, что вам нужно добавить методы в базовый класс, но вы не можете касаться базового класса. И что поделать с этим?

Шаблон проектирования, который позволяет решить проблему такого рода, называется "визитер" (это последний шаблон в книге "Design Patterns"), и он основывается на схеме двойной диспетчеризации, показанной в последнем разделе.

Шаблон визитера позволяет вам расширить интерфейс первичного типа путем создания отдельной иерархии классов типа Visiter для виртуализации операций, выполняемых над первичным типом. Объекты первичного типа просто "принимают" визитера, затем вызывают член-функцию визитера, выполняющую динамическое связывание.

//: visitor:BeeAndFlowers.java
// Демонтсрация шаблона "визитер".
package visitor;

import java.util.*;

import junit.framework.*;

interface Visitor {
  
void visit(Gladiolus g);
  
  
void visit(Runuculus r);
  
  
void visit(Chrysanthemum c);
}

// Иерархия Flower не может быть изменена:
interface Flower {
  
void accept(Visitor v);
}

class Gladiolus implements Flower {
  
public void accept(Visitor v) {
     
v.visit(this);
  
}
}

class Runuculus implements Flower {
  
public void accept(Visitor v) {
     
v.visit(this);
  
}
}

class Chrysanthemum implements Flower {
  
public void accept(Visitor v) {
     
v.visit(this);
  
}
}

// Add the ability to produce a string:
class StringVal implements Visitor {
  
String s;
  
  
public String toString() {
     
return s;
  
}
  
  
public void visit(Gladiolus g) {
     
s = "Gladiolus";
  
}
  
  
public void visit(Runuculus r) {
     
s = "Runuculus";
  
}
  
  
public void visit(Chrysanthemum c) {
     
s = "Chrysanthemum";
  
}
}

// Add the ability to do "Bee" activities:
class Bee implements Visitor {
  
public void visit(Gladiolus g) {
     
System.out.println("Bee and Gladiolus");
  
}
  
  
public void visit(Runuculus r) {
     
System.out.println("Bee and Runuculus");
  
}
  
  
public void visit(Chrysanthemum c) {
     
System.out.println("Bee and Chrysanthemum");
  
}
}

class FlowerGenerator {
  
private static Random rand = new Random();
  
  
public static Flower newFlower() {
     
switch (rand.nextInt(3)) {
     
default:
     
case 0:
        
return new Gladiolus();
     
case 1:
        
return new Runuculus();
     
case 2:
        
return new Chrysanthemum();
     
}
   }
}

public class BeeAndFlowers extends TestCase {
  
List flowers = new ArrayList();
  
  
public BeeAndFlowers() {
     
for (int i = 0; i < 10; i++)
        
flowers.add(FlowerGenerator.newFlower());
  
}
  
  
public void test() {
     
// It's almost as if I had a function to
      // produce a Flower string representation:
     
StringVal sval = new StringVal();
      Iterator it = flowers.iterator
();
     
while (it.hasNext()) {
         ((
Flower) it.next()).accept(sval);
         System.out.println
(sval);
     
}
     
// Perform "Bee" operation on all Flowers:
     
Bee bee = new Bee();
      it = flowers.iterator
();
     
while (it.hasNext())
         ((
Flower) it.next()).accept(bee);
  
}
  
  
public static void main(String args[]) {
     
junit.textui.TestRunner.run(BeeAndFlowers.class);
  
}
}
// /:~