next up previous
Next: Traversal Strategies Up: Demeter/Java User Manual Version Previous: Class Dictionaries

Subsections

Behavior Specification

  The behavior specification  describes the behavioral relations between classes, i.e. the methods. By specifying the behavior separately from the structure, you can program adaptively, so that your program is easier to evolve.

By convention, behavior is defined in files with the extension .beh. Each of these files may have several sets of methods, grouped according to some criteria, but even methods for the same class may be spread across multiple behavior files.

Throughout most of this chapter we will use the example behavior file below, search.beh, based on the conglomerate class dictionary presented in the beginning of chapter [*]. This behavior file searches for a given employee name, printing out the name of the company where (s)he works and her/his category (manager or staff), if such employee is found, and returning the corresponding Employee object.

Conglomerate {
  traversal allEmployees(EmpSearchVisitor) {
    to { Manager, Staff };
  }
}    

EmpSearchVisitor {
  (@ Name company_name; @)

  before Company (@ company_name = host.get_name(); @)

  before Manager (@ title = "Manager"; @)
  before Staff (@ title = "Staff"; @)

  after Employee (@
    if (host.get_name().equals(emplname)) {
      System.out.println("Found employee in company: "
                         + company_name + ": " + title);
      return_val = host;
    }
  @)

  around -> *,subsidiaries,* (@
    // Save the parent company's name while we're in the subsidiaries.
    Name parent_company_name = company_name;
    subtraversal.apply();
    // Restore the current company's name when we're done.
    company_name = parent_company_name;
  @)
}

Conglomerate {
  public Employee searchForEmployee(Name emplname)
    = allEmployees(EmpSearchVisitor);
}

Name {
  public boolean equals(Object name) (@
    return name instanceof Name && ((Name) name).get_n().equals(n);
  @)
  public String toString() (@ return n.toString(); @)
}

A behavior file consists of sets of methods, with each set contained in a class specification. Formally, a behavior file has the following syntactic structure:

$\langle$BehaviorFile$\rangle$ ::= $\langle$ClassBehavior$\rangle$*
$\langle$ClassBehavior$\rangle$ ::= $\langle$ClassNames$\rangle$ "{" $\langle$Method$\rangle$* "}"
$\langle$ClassNames$\rangle$ ::= $\langle$ClassName$\rangle$ | "{" $\langle$ClassName$\rangle$ { "," $\langle$ClassName$\rangle$ }* "}"
where $\langle$ClassName$\rangle$ refers to a class defined in the class dictionary. You can use the set notation to add identical behavior to a set of classes at once, e.g.

{ Conglomerate, Employee, Salary } {
    public String toString() (@
	StringWriter w = new StringWriter();
	toAll(new PrintVisitor(new PrintWriter(w, true)));
	return w.toString();
    @)
    traversal toAll(UniversalVisitor) { to *; }
}

will define toString

The different types of methods are defined below.

Defining Traversal Methods

  A traversal method  is a method that directly performs a traversal across an object graph, to which a visitor object (or set of visitor objects) can be attached to perform behavior during the traversal. It consists of three parts:

1.
Name.
The name of the traversal method, which can be invoked like any ordinary method.
2.
Visitors.
A list of visitor classes--the parameters to the traversal method.
3.
Strategy.
A traversal strategy to follow when traversing the object graph.

In our example, allEmployees is a traversal method:

  traversal allEmployees(EmpSearchVisitor) {
    to { Manager, Staff };
  }

This defines a method called allEmployees that takes a single EmpSearchVisitor object as an argument, and performs a traversal following the strategy to { Manager, Staff }. The source of the traversal is the class in whose context the traversal method is defined--in this case, Conglomerate.

Formally, a traversal method has the following syntactic structure:


$\langle$TraversalMethod$\rangle$ ::= "traversal" $\langle$methodName$\rangle$ 
		 		 "(" [$\langle$visitorName$\rangle$ { "," $\langle$visitorName$\rangle$ }*] ")"
		 		 "{" $\langle$StrategyExpression$\rangle$ "}"
The syntax and semantics of $\langle$StrategyExpression$\rangle$ is described in full detail in Chapter [*].

In our example, the allEmployees traversal method is used in the searchForEmployee adaptive method (described in more detail in [*] below); however, it could also be invoked manually, as follows:

    EmpSearchVisitor v = \ldots;
    Company c = \ldots;
    c.allEmployees(v);

In particular, a traversal method may be invoked with any visitor object whose class extends EmpSearchVisitor; this allows re-use of traversal methods with different visitors that implement different behavior along the traversal.

Defining Visitor Classes and Methods

 

A visitor class  is very much like the Visitor design pattern described in [Gamma et.al.]. It is used to perform behavior while traversing an object graph with a traversal method.

Visitor methods  are the methods on a visitor class that specify the behavior to be performed at each class along the traversal. Any class that defines visitor methods can be used as a visitor class; in other words, a visitor class is just an ordinary class that happens to define visitor methods. However, a visitor class may be specially marked as such in the class dictionary, with the visitor keyword before the class name:

visitor EmpSearchVisitor = ...
Alternatively, a number of classes can be marked at once with a visitors...endvisitors block:
visitors
Visitor1 = ...
Visitor2 = ...
...
endvisitors

There are several different types of visitor methods:

1.
Before.
A before method is invoked as soon as a traversal arrives at an object of the given class, before the traversal continues on.
2.
After.
An after method is invoked after a traversal has finished traversing an object of the given class, on its way back out of the object.
3.
Around.
An around method is invoked in an object of the given class instead of continuing along the traversal. The continuation of the traversal must be explicitly invoked if desired, by using the apply method on the special variable subtraversal, like so:

    around Department (@
        if (host.matches()) {
            subtraversal.apply();
        }
    @)

Defining Adaptive Methods

 

An adaptive method  is much like a propagation pattern  in The Book and the Demeter/C++ system. It consists of three parts:

1.
Signature.
Declaration of the adaptive method, including its name, parameters, and return type.
2.
Traversal.
Specification of a traversal over the class dictionary.
3.
Visitors.
Specification of visitor classes to be used to peform behavior along the traversal.

A more detailed explanation about each of these parts is given below.

Signature

In the searchForEmployee adaptive method, the signature is:

void searchForEmployee(String emplname)
The syntax for the adaptive method signature is exactly that of a regular Java method signature.

Traversal

In the searchForEmployee adaptive method, the traversal specification is:

= allEmployees
This means the searchForEmployee adaptive method will follow the allEmployees traversal along the object graph. In this case, the traversal specification is a reference to the name of a traversal method, defined elsewhere (see section [*] below). The traversal may also be defined in-line, if it is not going to be reused outside of this adaptive method (see section [*] below).

Visitors

In the searchForEmployee adaptive method, the visitor specification is:

(EmpSearchVisitor);
This means that the adaptive method will perform the behavior specified in the EmpSearchVisitor visitor class while following the traversal. In this case, the visitor specification is a reference to the name of a visitor class defined elsewhere (see section [*] above). In general, a visitor specification may be a comma-separated list of visitor class names. A visitor may also be defined in-line, if it is not going to be reused outside of this adaptive method (see section [*] below).

Putting the Pieces Together

There are two restrictions on putting together the pieces of an adaptive method.

1.
The visitors must match the traversal method signature. There must be the same number of visitors as parameters to the traversal method, and each visitor class must be the same as or a subclass of the corresponding parameter (or an implementation of the parameter, if the parameter is an interface). See section [*] below.
2.
The first visitor class must define parts with the same names and types as the arguments to the adaptive method. If the adaptive method has a return type, the first visitor class must also define either a part named return_val or a return method with the given return type. See section [*] below.

When defining adaptive methods inline, these restrictions are taken care of automatically; see section [*].

Invoking an Adaptive Method

An adaptive method is invoked just like a normal Java method with the given signature. For example, the searchForEmployee method would be invoked as follows:

emp = conglom.searchForEmployee("Fred");
assuming that conglom is of type Conglomerate (perhaps an object returned from invoking Conglomerate.parse()).

When an adaptive method is invoked, the following sequence of events occurs:

1.
Visitor objects corresponding to the given visitors are instantiated by calling the visitor classes' default constructors.
2.
For each parameter in the adaptive method signature, the corresponding part on the first visitor object is set to the value of the corresponding argument in the invocation.
3.
The start method on each visitor object is called with the receiver object of the invocation as the argument.
4.
The traversal method is called on the receiver object with the visitor objects as arguments.
5.
The finish method on each visitor object is called with the receiver object of the invocation as the argument.
6.
If the return type of the adaptive method is not void, then if the first visitor has a return method, it is called and its return value is returned from the adaptive method; otherwise the return_val part of the first visitor is returned.

Conceptually, the searchForEmployee adaptive method is equivalent to the following Java method:

Employee searchForEmployee(String emplname) {
    SearchVisitor v = new SearchVisitor();
    v.set_emplname(emplname);
    v.start(this);
    this.allEmployees(v);
    v.finish(this);
    return v.get_return_val();
}

Inlining Traversals and Visitors

  ...

Defining Verbatim Methods

A verbatim method  is just an ordinary Java method.

Other Verbatim Declarations

A verbatim declaration  can be an arbitrary block of Java declarations between "(@" and "@)"; these declarations are attached directly to the enclosing class. They can be used to define fields and methods directly, without being otherwise visible to Demeter/Java.

Derived Part Accessor Methods

Derived parts allow for portions of a program's structure to be represented functionally, instead of directly by data. Used correctly, this functional representation can greatly increase the flexibility of your adaptive program.

Conceptually, a derived part has its associated get_xxx and set_xxx methods replaced by user code. The user code can be as complicated as needed to compute the part (in the case of the get method) or update the containing object's state (in the case of the set method).

Making a Part Derived

A part is marked as derived by providing an overriding get or set method for it in one of a program's behavior files. For example, if you have a Company class, and would like for it to have a derived Employee member, you would provide methods like:

Company {
  get employee (@
    ...
    return computedEmployeeObject;
  @)

  set employee (@
    ...
  @)
}

These methods will be turned into methods on the Company class at adaptive compile time, so they have access to all of the Company object's members to do their work.

Notice that the get method returns something. All get methods need to return a value which is an object of the class of the member they are defined for (an object of class Employee in the example).

The variable dest will be bound to the argument passed to the set_xxx method during an execution of a set method.

You need to be careful that you don't have circular dependencies in computing derived parts. For example, a program will not work correctly if part a's get method references part b's get method, which references part a's get method.

A Derived Part Example

Here is a brief example that should help clarify things. It is an example of how derived parts can be used to allow for representation independence, and uses complex numbers as its domain.

The example's class dictionary looks like this:

(@ import java.lang.Math; @)

Main = .

ComplexNumber = RealPart ImagPart
                Radius Angle .

RealPart = <real> float .
ImagPart = <imag> float .
Radius = <radius> float .
Angle = <angle> float .

In its behavior one of ComplexNumber's representations (polar vs. rectangular coordinates) will be marked as derived, and will be computed from its other representation as needed. The nice thing about using derived parts in this way is that the rest of the program doesn't need to know which representation is being used.

Here is the portion of the program's behavior that specifies the derived parts:

ComplexNumber {
  get radius (@
    return new Radius((float)Math.sqrt((get_realpart().get_real() * 
                                        get_realpart().get_real())
                                     + (get_imagpart().get_imag() * 
                                        get_imagpart().get_imag())));
  @)

  get angle (@
    return new Angle((float)Math.atan2(get_imagpart().get_imag(),
                                       get_realpart().get_real()));
  @)

  set radius (@
    float r = dest.get_radius();
    float a = get_angle().get_angle();

    set_realpart(new RealPart(r * (float)Math.cos(a)));
    set_imagpart(new ImagPart(r * (float)Math.sin(a)));
  @)

  set angle (@
    float r = get_radius().get_radius();
    float a = dest.get_angle();

    set_realpart(new RealPart(r * (float)Math.cos(a)));
    set_imagpart(new ImagPart(r * (float)Math.sin(a)));
  @)
}

next up previous
Next: Traversal Strategies Up: Demeter/Java User Manual Version Previous: Class Dictionaries
Joshua C. Marshall
12/2/1998