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:
whereBehaviorFile
::=
ClassBehavior
*
ClassBehavior
::=
ClassNames
"{"
Method
* "}"
ClassNames
::=
ClassName
| "{"
ClassName
{ ","
ClassName
}* "}"
{ 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.
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:
The syntax and semantics ofTraversalMethod
::= "traversal"
methodName
"(" [
visitorName
{ ","
visitorName
}*] ")" "{"
StrategyExpression
"}"
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.
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:
around Department (@
if (host.matches()) {
subtraversal.apply();
}
@)
An adaptive method is much like a propagation pattern in The Book and the Demeter/C++ system. It consists of three parts:
A more detailed explanation about each of these parts is given below.
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.
In the searchForEmployee adaptive method, the traversal specification is:
= allEmployeesThis 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
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
There are two restrictions on putting together the pieces of an adaptive method.
When defining adaptive methods inline, these restrictions are taken
care of automatically; see section
.
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:
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();
}
A verbatim method is just an ordinary Java method.
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 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).
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.
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)));
@)
}