Read an Excerpt
Part VI: Design Templates
Interface for Association Templates
For each association, the OOPL interface consists of a set of operations that access and update the association. The exact terms and structure of these operations depend on the cardinalities of the relevant mappings.
In general, a single-valued mapping requires two operations: an accessor and a modifier. The accessor operation returns the object to which a given object is mapped. The modifier operation changes the mapping for a given object by reassigning the mapping pointer from one object to another. Access requests, then, require no input parameters. Modification requests, however, require an input parameter that specifies the object to which the mapping must now point. Thus, for Fig. 17.2, the Employee class would have two operations. In C++, no standard naming convention exists. Here, many programmers use get or set somewhere in the name. For example, the names getEmployer and setEmployer (Organization org) could be used to access and modify the employer mapping. The names getEmployer and setEmployer are the most natural. However, some prefer employerSet and employerGet, because both operations will appear together In an alphabetically sorted browser. In Smalltalk, both operations are conventionally given the mapping name. Here, modifiers are distinguished from accessors by the presence of a parameter. Therefore, the Employee class would have get and set operations named employer and employer: anOrganization.
Multivalued mappings require three operations - again, with one accessor. Single-valued accessors return just one object. Multivalued accessors, however, return aset of objects. (All multivalued mappings are assumed to be sets unless otherwise indicated. The interface for non-sets will be different and is beyond the scope of this month's column.) Multivalued modifiers require two operations - one to add an object to a set, the other to remove an object. The accessor will usually be named in the same way that a singlevalued mapping is named. However, a plural form is recommended to reinforce its multivalued nature-for example, employees or getEmployees. Modifiers would take the form of AddEmployee (Employee emp), RemoveEmployee (Employee emp), or employeeAdd: anEmployee, employeeRemove: anEmployee.
Modifiers, whether single-valued or multivalued, should also ensure that the constraints are met. For example, the SetEmployer operation should ensure that the employer mapping of Employee is not set to null. In other words, the modifier should ensure that both minimum and maximum cardinality constraints are met. Any other constraints, such as invariant, tree, and user-defined constraints, should also be enforced at this time.
Type checking should also be performed. For example, if a SetEmployer: anOrganization operation is requested, the object supplied via the anOrganization parameter must always be an Organization object. If type checking is not built into the programming language, extra code can be added to the modifier operations to ensure type integrity.
Association template option 1: using pointers in both directions
In this option, mappings are implemented by pointers from both participating classes. If a mapping is single valued, there is a single pointer from one object to another. For example in Fig. 17.3, each Employee has a single pointer to his employer. If a mapping is multivalued, the object will have a set of pointers to the other objects. In Fig. 17.3, NASA points to a set of pointers which, in turn, contains pointers to Peter, Jasper, and Paul. For languages that support containment, an object may hold its set of mapping pointers internally rather than point to an external collection. Containment, therefore, has implications for space requirements. Since pointer sets can dramatically increase in size, an object's size can swell. Single-valued mappings can also use containment. Here, the actual object will be stored internally, instead of a pointer to that object. Typically, single-valued containment is limited to storing fundamental objects internally, such as Integer or Date objects. (Fundamental objects will be discussed later in option 6.)
In option 1, the accessor operations are relatively straightforward. For a single-valued mapping, the accessor merely returns a reference to the mapped object. For a multivalued mapping, the accessor returns a set of references. However, it should not return the set of references. If it did return the set, the set's user could change the set's membership-thereby violating encapsulation. The encapsulation boundary should include all sets implementing multivalued mappings. One solution is returning a copy of the set. Thus, if any alterations are made, they do not affect the actual mapping. However, this may incur a significant time overhead for large sets. An alternative is to use a masking class. A masking class is a simple class that has a single field containing the set. Only those operations that are permitted on the contained set are defined in the masking class. This way modifications can be blocked. Another alternative, particularly for C++ implementations, uses iterators as described by Gamma [Gamma, 1995]. Iterators provide a highly flexible way to access the elements of a multivalued mapping without exposing underlying representation.
Since two pointers implement each relationship, modifiers should maintain a two-way, or referential, integrity. Thus, a modifier called to change Peter's employer to IBM must not just change Peter's pointer to IBM. It must also delete the inverse pointer to Peter in NASA'S employees' set and create one in IBM's employees' set.
This template option has both benefits and drawbacks. Its accessor navigation is fast in both directions. However, ensuring referential integrity requires extra processing time. So, while this option provides fast access, modification requires extra time. Additionally, the technique to ensure referential integrity is not trivial. However, once a solution has been chosen, replication is easy. Another disadvantage lies in the space required for this option. Not only are pointers required in both directions, but multivalued mappings can require large sets....