On this page:
Visitors
8.13

Recitation 10a: Visitors🔗

Related files:
  Lab10a-Visitors.zip  

Goals: Learn how to design and use a visitor for a union of classes.

Visitors🔗

We often do not know what kinds of methods the programmers need to define for their lists of objects. Furthermore, they may have to define one specific method for their list that is not applicable to any other list of objects.

For example, we may want to use these classes for our circularly defined data. So we may want to add the method addBook to the classes that represent the list of authors (or addRole to the classes that represent the list of movie actors).

To make the library extensible, the designers need to provide ways for adding new methods at the time the programmer wants to use it.

One way to make the self-referential union of classes extensible is by defining a visitor interface and adding to the union a method that invokes the methods of the visitor interface supplying its own internal data as arguments. This sounds very complicated. We will illustrate this on an example.

Start with a new project Lab10a-Visitors and add to it all files from the Lab10a-Visitors.zip archive linked above. You should have the following files:

Book.java

Song.java

Image.java

ILo.java

ExamplesLists.java

ILoVisitor.java

Here is an example of a visitor interface for the classes that define a list of objects. As with our other function-object interfaces, argument types come before the result type:

// Represents a function from A to R

interface IFunc<A, R> {

  R apply(A arg);

}

 

// A visitor for ILo<T> that produces a result of type R

interface ILoVisitor<T, R> extends IFunc<ILo<T>, R> {

  // visit an empty list

  R visitMt(MtLo<T> mt);

 

  // visit a nonempty list

  R visitCons(ConsLo<T> cons);

}

The visitor for a union of data types contains one method for every class in the union. Each method’s name identifies the class it visits, and its argument is an instance of that class. Here, visitMt consumes an MtLo<T> and visitCons consumes a ConsLo<T>. This visitor is a function over the whole ILo<T> union, so it extends IFunc<ILo<T>, R> and implements apply by asking the given list to accept it.

We now add to the classes MtLo<T> and ConsLo<T> the hooks, the methods that accept the instance of the visitor class and invoke the appropriate methods defined there:

// in the interface ILo<T>:

// accept the visitor that produces a result of the type R

<R> R accept(ILoVisitor<T, R> visitor);

 

// in the class MtLo<T>:

// accept the visitor that produces a result of the type R

public <R> R accept(ILoVisitor<T, R> visitor) {

  return visitor.visitMt(this);

}

 

// in the class ConsLo<T>:

// accept the visitor that produces a result of the type R

public <R> R accept(ILoVisitor<T, R> visitor) {

  return visitor.visitCons(this);

}

The example included in the Lab10a-Visitors.zip files defines a visitor that represents a method that computes the total download time for all files in the list of image files.

The visitor class that implements the ILoVisitor<Image, Integer> interface is defined as follows:

// A visitor that computes the total download time for all files

// in the list of image files

class ILoImageDownloadTimeVisitor

    implements ILoVisitor<Image, Integer> {

  int speed;

 

  ILoImageDownloadTimeVisitor(int speed) {

    this.speed = speed;

  }

 

  // apply this visitor to the given list

  public Integer apply(ILo<Image> images) {

    return images.accept(this);

  }

 

  // compute the download time for an empty list

  public Integer visitMt(MtLo<Image> mt) {

    return 0;

  }

 

  // compute the download time for a nonempty list

  public Integer visitCons(ConsLo<Image> cons) {

    return cons.first.fileSize / this.speed + cons.rest.accept(this);

  }

}

The following examples show how we can use this method with our lists of data:

ILoVisitor<Image, Integer> imageDownloads =

    new ILoImageDownloadTimeVisitor(200);

 

// test the use of the ILoImageDownloadTimeVisitor

void testILoImageDownloadTimeVisitor(Tester t) {

  t.checkExpect(this.mtloi.accept(this.imageDownloads), 0);

  t.checkExpect(this.ilist2.accept(this.imageDownloads), 287);

}

We see that the methods defined in the class that implements the visitor have the same structure as those originally defined inside of the MtLo... and ConsLo... classes. The instance of the list then invokes the new method by applying the accept method with the appropriate visitor as its argument.

Finish this problem as a part of your next homework.