7. Implementing Collaboration Pairs

Don’t learn the tricks of the trade. Learn the trade.

Anonymous

Nuts and Bolts

This chapter shows how to translate the collaboration patterns into programming code in the Java and Squeak Smalltalk languages. The next chapter shows how to build on this code to implement business rules. While there are many ways to implement an object model, and many styles for coding objects, this chapter shows the advantages of using a consistent approach. By consistently using collaboration patterns and consistently organizing business rules into property and collaboration rules, it is rather straightforward to construct a coding process for implementing the patterns and rules.

The benefits of using a consistent collaboration-based coding process are:

• Faster code development

• Efficient code reviews

• Sound business rule management

There is no magic bullet for faster code development. In general, coding is difficult and requires much thought and organization; however, occasionally we uncover coding tasks that are repetitive and mechanical, and these lend themselves to a repeatable process. Two such tasks are coding an object definition and setting up collaboration rules. These tasks can be systematized into repetitive processes, thus enabling a developer to quickly lay the infrastructure needed to build the real guts of the system—the business services.

Code reviews are something lots of people will tell you are needed, but few of those people actually do them. If everyone is following a consistent approach for defining objects and setting up collaboration rules, then the code produced can be reviewed methodically and thoroughly by peers familiar with the standard. By having something concrete to compare against the code, the reviews can be more straightforward and less uncomfortable.

Business rules change as markets change and e-businesses evolve. Having a consistent methodology for coding collaboration rules allows us to quickly scan the code to find rules that need updating and locate where new rules should be added. We can then make changes without upsetting the rest of the code base. Additionally, since the rules are organized in a consistent manner, the rules can be extracted into a more flexible, pluggable architecture.

Object Definitions

An “object definition” is the code needed to define the properties, services, and collaborations for a single kind of object in the object model. Object definitions are implemented in non-object-oriented programming languages by mapping the object paradigm onto the languages’ constructs. Regardless of how a language supports objects, the “parts” of the object definition are remarkably the same. This consistency means that a standard checklist for defining objects will work within multiple languages.

Defining Objects: DIAPER

The “DIAPER” process is a six-step process for creating an object definition with the minimum infrastructure to support collaboration rules and business services. We devised the mnemonic while teaching Smalltalk to COBOL programmers. The name helped them grasp the object-oriented programming paradigm. We have translated the DIAPER process to C++ and Java and have used it to great effect in our own development efforts. The DIAPER steps are described in Table 7.1, and a summary version is included in Appendix B.

Table 7.1. DIAPER Steps for Creating Object Definitions

image

As discussed earlier, all 12 collaboration patterns can be summarized by three fundamental patterns: (1) generic – specific, (2) whole – part, and (3) transaction – specific. Using the three fundamental patterns along with the DIAPER steps gives us three coding templates capable of implementing all 12 collaboration patterns. The remainder of this chapter presents the three coding templates.

Generic – Specific Object Definitions

This section discusses guidelines for implementing generic – specific collaborations. Some of these apply to other fundamental patterns, and some only apply to generic – specific collaboration. Subsequent sections apply the guidelines to Java and Squeak implementations.

Collaboration patterns described by the generic – specific pattern are:

actor – role

item – specific item

composite transaction – line item

Specifying Object Inheritance

Any coding template for the generic – specific pattern must accommodate the object inheritance mechanism, which specifies the properties and services in the generic that are accessible from the specific object. What object inheritance really means is that certain determine mine and analyze transactions services available in the generic are also available in the specific.1 Restating this in implementation terms, object inheritance requires the specific’s object definition to include some of the same services in the generic’s object definition. The code is not the same in each object definition, but services with the same names have the same inputs and outputs in each object definition. This description of a service in terms of its name, inputs, and outputs is called a “service signature.” A service that is object inherited has the same service signature in the child object definition as it does in the parent object definition.

EXAMPLEA person can play different roles—chair, admin, or member—on many different teams, and each person has a name, title, and email. Using the collaboration patterns, a person object (actor) serves as the parent to some number of team member objects (roles). See Figure 7.1.

Figure 7.1. Person – TeamMember object model with Person accessor services showing.

image

Object inheritance requires that the object definitions for a Person and for a TeamMember both contain methods with the following signatures:

image

In the Person object definition, the getTitle service is described in pseudo-code to return a Person object’s title property:

String getTitle { return my title }

In the TeamMember object definition, the getTitle service is described in pseudo-code to return the result of the TeamMember object asking its Person object to get its title.

String getTitle { ask my person to return its title }

Define: The Parent Profile Interface

While the previous chapter presented strategies for determining which services are object inherited, the Person – TeamMember example highlights the need for a mechanism capable of specifying method signatures for those object inherited services. In object-speak, the construct for specifying expected behavior is an “interface.”2 Technically, an interface is nothing more than a collection of method signatures. Some people understand interfaces, and some people don’t so be careful when using them in your analysis object models. When an object definition is associated with an interface, the object definition must implement a matching method for each method signature in the interface. In object-speak, the object definition implements the interface, and its objects exhibit the interface.

Object inheritance uses an interface to specify parent behaviors object inherited by its child objects. This interface is called a profile interface because any child exhibiting the interface takes on the appearance of its parent. Both parent and child object definitions implement the profile interface.

EXAMPLEIn the Person – TeamMember collaboration, the IPersonProfile interface contains all the object inheritable services of Person. The Person and TeamMember object definitions show that they implement the IPersonProfile interface by including it in their service sections.3 This shorthand implies that all the services in the interface are also included in the Person and TeamMember object definitions. See Figure 7.2.

Figure 7.2. Both Person and TeamMember implement the IPersonProfile interface.

image

Define: The Conduct Business Interface

Interfaces help achieve object pluggability, which means that different objects can be plugged into the same system component. The profile interface helps object pluggability by allowing a child object to pass itself off as its parent in read-only scenarios. Either a parent or a child object can be plugged into a system component designed to request the services described in the profile interface. In other words, the component is not hardwired to work with only one type of object. Instead, the system component is built to use an interface and can work with any object exhibiting the interface. A system component built to use a profile interface has read-only pluggability.

Read-only pluggability is not enough for frameworks and systems expected to grow over time. These require pluggability in handling collaborations and performing conduct business services. To achieve full object pluggability, all the services of the objects must be described with interfaces.4 Interfaces that include conduct business services are called conduct business interfaces. Any object exhibiting a conduct business interface can be plugged into a system component built to use that interface. Business rules permitting, such a component can edit the object, change its collaborations, and kick off conduct business services. In object inheritance, the parent’s conduct business interface extends its profile interface. For a reference table highlighting the different responsibilities of the profile and conduct business interfaces, see Appendix B.

EXAMPLEDefine an interface, IPerson, that contains all the conduct business services and the non-profile determine mine and analyze transactions services. The arrow indicates that the IPerson interface extends the IPersonProfile interface and that IPerson enforces all the method signatures enforced by IPersonProfile. See Figure 7.3.

Figure 7.3. The Person conduct business interface extends the Person profile interface.

image

Define: The Child Profile Interface

In many parent – child collaborations, the child object may later become a parent to another kind of object. This is especially common with systems involving many types of roles where a role serves as the parent to a more specialized role. To prepare for this, frameworks and evolving systems make child objects parent-ready by defining two interfaces for them, a profile interface and a conduct business interface. With a profile interface of its own, the child object definition can support object inheritance of its own properties and services.

EXAMPLEDefine profile and conduct business interfaces for TeamMember (see Figure 7.4). The TeamMember profile interface extends the Person profile interface. If TeamMember ever becomes a parent, the new child profile interface will extend the TeamMember profile interface.

Figure 7.4. Profile and conduct business interfaces for TeamMember.

image

Initialize: Initialization Rules

Often, a type of object cannot exist without certain data or a certain collaborator. Rules specifying information an object needs to exist are called initialization rules. A specific always has an initialization rule requiring a generic, because a specific cannot be created without a generic parent. A generic may have an initialization rule for properties it needs to exist.

EXAMPLEA person cannot exist without a valid name. A videotape cannot exist without its video title, which is also its parent object.

Initialize: Object Construction

Object construction is the process by which objects are created and initialized. Initialization sets the newly created object’s properties to default values or to given values supplied when the object is created. Because objects frequently have many properties, it is impractical to allow given values to be supplied for all the properties when creating a new object. We use object initialization rules to select which properties can be supplied given values during object creation. With this approach, an object construction method can only accept given values for the properties and collaborators mentioned in the initialization rules. This approach cuts down on “constructor explosion,” where an object definition has lots of construction methods based on many permutations of the object’s properties and collaborators.

Access: Object Definition Variables

Objects require storage to hold their properties and collaborations, and they get their storage allocations from the variables specified within their object definitions. The decision about whether a property is stored or derived is made either during the design or during the implementation of the object definition. Derived properties will be determined by a method; stored properties will be held in a variable specified in the object definition.

An object definition usually includes references to its collaborators.5 In the generic – specific collaboration, how many specific collaborators a generic knows can vary, so there are three approaches for referencing specific collaborators of a generic. First, if a generic knows only one specific of a particular type, then it includes a variable to store a reference to that specific. Second, if a generic knows multiple specifics of the same type, it can store a homogeneous collection of those. Third, if a generic knows multiple specifics of different types, it can store a non-homogeneous collection of specifics. This book uses the first and second approaches. While the third approach has merit in its flexibility and extensibility, it requires too much searching to locate an appropriate specific. In general, the object definition of a generic includes a collection of specifics only if its specifics are all the same type. Otherwise, a generic should have a distinct variable for each distinct specific type. Define a specific with a variable to contain its generic.

EXAMPLE: actor – role—A domain allows a person to take on employee, customer, and broker roles. A person can have at most one employee role, one active customer role, and any number of broker roles. The person object definition includes separate collaboration variables for each role type. The employee variable, if set, references a single employee object. The customer variable references a collection of customer objects, but only one can be active. The broker variable references a collection of broker objects. Each role includes a collaboration variable that references its person object.

EXAMPLE: item – specific item—A video store with both DVDs and videotapes for rent describes each movie with a movie title description. A movie title may have several video versions: wide-screen, standard format, Spanish subtitles, etc. Each video version is described with its own video title, which is a child of the movie title. DVD titles describe different DVD versions (see Figure 7.5). The movie title object definition has two collaboration variables: one references its collection of video title child objects, and the other references a collection of DVD titles. Each video title and DVD title includes a collaboration variable that references its movie title.

Figure 7.5. A MovieTitle with two kinds of child objects.

image

EXAMPLE: composite transaction – line item—A point-of-sale transaction for a retail clothing store includes both sale and return line items. The point-of-sale transaction object definition has two collaboration variables: one references its collection of sale line items objects, and the other references a collection of return line items. Each line item includes a collaboration variable that references its point-of-sale transaction.

Access: Collaborator Accessors

Collaborator accessors add, remove, and get collaborators.6 All generics add and remove specifics with the following accessors:

image

A generic with a single specific has the following accessor:

image

A generic with multiple specifics has the following accessors: The second one is optional, and used as needed by the domain.

image

A specific has the following accessor methods:

image

Print: Describing Objects

Don’t confuse printing an object with displaying an object. Print methods are used primarily for debugging and testing. A useful print method returns a description of an object in terms of its properties and collaborators.

The smart technique for printing an object’s collaborators is to ask each collaborator to print itself; however, with two-way collaborations, print methods calling other print methods leads to infinite loops. Yikes! Clearly, only one collaborator should be using the print service of the other collaborator, but which one?

Here is a good rule of thumb: Put work in the most specific collaborator. The specific has the most knowledge and so putting the work there places it closer to the data. Also, a generic may have many kinds of specifics and each may require a different implementation of the work.

A generic should print its own properties, but should not ask its specific to print itself. On the other hand, a specific should print its own properties, and ask its generic to print itself.

Equals: Detecting Duplicate Objects

Frequently, object models have collaboration rules that prohibit duplicate objects. Most systems don’t want to add an object to a collection twice, or at least want smart processing to handle it when it happens. The trick is having a way to compare the object being added against the ones already there.

What makes two objects equal depends on collaboration rules and design considerations. Two persons with the same Social Security Number may be considered equal, but two with the same date of birth are not. Business rules and design considerations determine which properties and collaborations come into play when comparing objects for equality.

Good object think dictates that an object can determine for itself if it is equal to another object. Within the equals method, the object doing the work compares its properties and collaborators against those of the other object. Much like the print method, infinite loops in the equals method are a possibility with two-way collaborations. Again, let the specific do the work.

When a generic compares itself to another generic, their specifics don’t enter into it. However for a specific, the generic holds a significant portion of its state. So when a specific compares itself to another specific, the generics must also be compared.

Run: Test Objects

Test objects are objects populated with realistic property values, and are, when possible, associated with collaborators with realistic data. Test objects do not replace full-fledged test cases and testing harnesses, but they do provide the following:

• Quick tests of the object infrastructure

• Building blocks for growing the system incrementally

• Instructive examples for understanding the object

Test objects live with the code, so they can be used by a developer to test code as it is written and when it is modified. Think unit testing. The point of test objects is to avoid the big-bang-test-at-the-end syndrome, and to allow testing even when test databases are not available.

After unit testing, another value of test objects is that they can be linked together according to the collaboration patterns in the object model. Connecting collaborators together allows testing of the collaboration rules and non-trivial business services.

New people coming on-board an expanding project need to ramp up quickly. Sometimes that means reading other people’s code. If that code contains test objects, then they can see examples as they read the code, and can, with little additional effort, write test cases to exercise the code.

Create test objects to illustrate interesting scenarios, to test state changes and border conditions on property values, and to create stereotypical objects for collaborations. Use the “Most Specific Carries the Loadprinciple (74) to code the test objects. Define a generic test object with realistic property values but without a specific collaborator; define a specific test object with realistic property values and a generic test object collaborator.

Generic – Specific Template

Putting all this together gives a concise coding template for generic – specific collaborations. What follows is an example of that template applied to an actor – role collaboration.

A quick note on terminology: From here on, we will refer to object definitions as classes, call the class inheritance generalization class the superclass, call the specialization class a subclass, and refer to coded services as methods.

Generic – Specific Example

For our generic – specific example, we implement the Person – TeamMember object model shown in Figure 7.4. This model is an example of a generic (Person) with many specifics (TeamMembers) of the same type.

Business Rule Violations

Objects resist accepting an illegal property value, violating a collaboration rule, or performing a service while in an invalid state. These actions would violate business rules. How objects handle business rule violations is an important design consideration when translating object models into code. Objects in object models do not have presentation logic, so they cannot beep, flash, or present a dialog box. Rather, if asked to break a business rule, the appropriate response for one of these objects would be to stop processing and raise an exception describing the violation. Other objects within the system can decide how to handle the business exception, by notifying a user, logging it in a report, or notifying some external system.

Java Business Exception Class

This example defines a simple subclass of the Java Exception class to throw messages when illegal values are encountered or business rules are violated (see Listing 7.1).

Listing 7.1. Java class for BusinessRuleException.

public class BusinessRuleException extends Exception
{
   public BusinessRuleException() { super(); }
   public BusinessRuleException( String message) { super(message);
                       }
}

Squeak Business Exception Class

In Squeak, business exception classes are subclasses of the Error class hierarchy. No extra methods are necessary as the inherited ones will do everything we need (see Listing 7.2).

Listing 7.2. Squeak class for BusinessRuleException.

Error subclass: #BusinessRuleException
  instanceVariableNames: ''
  classVariableNames: ''
  poolDictionaries: ''
  category: 'Nomination-Example'

Person: Java DIAPER

This section uses Java to apply our coding template for implementing a generic object definition. The class implemented is the Person modeled in Figure 7.4. Immediately after this section, the same example is presented in Squeak.

Person Initialization Rules

A person has the following initialization rule: A person must have a name to exist.

Define: Person Profile Interface

The IPersonProfile interface is implemented by any class that has objects that assume a person profile. In Listing 7.3, both person objects and team member objects exhibit this interface.

Listing 7.3. Java profile interface for Person.

public interface IPersonProfile
{
  // ACCESSORS -- get properties
  public String getName();
  public String getTitle();
  public String getEmail();

  // DETERMINE MINE
  public boolean hasValidEmail();
}

Define: Person Conduct Business Interface

Only the Person class implements the IPerson interface. It consists of: (1) the methods that change the state of the object by setting properties or adding collaborators, and (2) the collaboration accessors that are not object inherited (see Listing 7.4). The accessors to get the team members are included in the conduct business interface because child objects do not object inherit their parent’s other children.7 Since these methods change the object, they throw business rule exceptions, except the collaboration get accessor.8 Also, according to the “How I See Youprinciple (71), collaborators refer to each other through their conduct business interfaces, so the collaboration accessors expect the team member conduct business interface as a parameter.

Listing 7.4. Java conduct business interface for Person.

public interface IPerson extends IPersonProfile
{
  // ACCESSORS -- set properties
  public void setName( String newName) throws BusinessRuleException;
  public void setEmail( String newEmail) throws BusinessRuleException;
  public void setTitle( String newTitle) throws BusinessRuleException;

  // ACCESSORS – collaborations
  public List getTeamMembers();
  public void addTeamMember( ITeamMember aTeamMember)
                    throws BusinessRuleException;
  public void removeTeamMember( ITeamMember aTeamMember)
                    throws BusinessRuleException;
}

Define: Person Class

To define the Person class, specify the superclass and interfaces implemented, and define the property and collaboration variables (see Listing 7.5). A Java collection object, ArrayList, is used to contain the person’s team members. To represent the person’s email address, we defined our own utility class, EmailAddress, with rules for validating email addresses.9

Listing 7.5. Java class specification for Person.

public class Person extends Object implements IPerson
{
  // DEFINE
  private String name;
  private String title;
  private EmailAddress email;
  private ArrayList teamMembers;
}

Initialize: Person Constructor

Initialization occurs when an object is created and sets the object’s properties to default or given values.10 Following the “Minimum Parameter Ruleprinciple (73), we use the person initialization rules to determine the information necessary to create a person object. Because these rules state that a person object cannot exist without a name, two facts become clear: (1) all constructors require a name parameter, and (2) every constructor must validate the name. Consequently, there is no default constructor for the class, and every constructor must be capable of throwing a business rule exception.

The Person class has one constructor that takes a name value and throws a business rule exception if the name is invalid (see Listing 7.6). The accessor method for setting the name is used to check the validity of the name, and this method throws the business rule exception if the name is bad. Business rule checking is not necessary for the other properties since the object is setting the property values itself. The email property is set to an “empty” email address.

Listing 7.6. Java constructor for Person.

public Person( String newName) throws BusinessRuleException
{
  this.setName(newName);
  this.title = new String();
  this.email = new EmailAddress();
  this.teamMembers = new ArrayList();
}

Accessing: Properties

Property accessors are public methods that get and set property values. The set accessors validate the new values and throw business rule exceptions when an illegal value is supplied. A person has accessors for its name, title, and email properties (see Listing 7.7). To the outside world, the email address is a string, thus the get accessor returns a string and the set accessor takes a string. Internally, the string is placed within an email address object that raises an exception if the string is not a valid email address. A special “remove” accessor sets the email address back to its “empty” value.

Listing 7.7. Jave property accessors for Person.

// ACCESSORS - get properties

public String getName()
{
  return this.name;
}

public String getTitle()
{
  return this.title;
}

public String getEmail()
{
  return this.email.getAddress();
}

// ACCESSORS - set properties

public void setName( String newName) throws BusinessRuleException
{
  if ((newName == null) || (newName.length() == 0))
     throw new BusinessRuleException("Name cannot be null or empty.");
  this.name = newName;
}

public void setTitle( String newTitle) throws BusinessRuleException
{
  if (newTitle == null)
     throw new BusinessRuleException("Title cannot be null.");
  this.title = newTitle;
}


public void setEmail( String newEmail) throws BusinessRuleException
{
  this.email.setAddress(newEmail);
}

public void removeEmail( ) throws BusinessRuleException
{
  this.email.setAddressEmpty();
}

Accessing: Collaborations

Collaboration accessors add, remove, and get collaborators. A person has collaboration accessors for its team member collaborators (see Listing 7.8). Collaboration rules are not being checked here; those checks will be added in the next chapter. However, this code does check the logic validation rules. For example, the add accessor throws an exception if there is an attempt to add a null team member object because that is a sneaky way of getting around the remove method. Objects don’t like sneaky code.

Similarly, the getTeamMembers method returns the team member objects in a list that cannot be modified.11 Returning an “unmodifiable” list prevents attempts to add or remove team members without using the accessor methods and checking the collaboration rules. For subclasses that may legitimately need the ability to add and remove from the list, a protected accessor returns a list iterator on the array list. Using a list iterator gives subclasses edit abilities, but not the ability to reset the variable.

Finally, for maximum flexibility, the parameters and return values are typed using interfaces rather than hardwiring them to be a particular class.

Print

Following normal Java coding standards, an object prints its properties and collaborations in its toString method. Under the “Most Specific Carries the Loadprinciple (74), the person object, which is a generic, does not print its team member collaborator, which is a specific (see Listing 7.9).

Listing 7.8. Java collaboration accessors for Person.

// ACCESSORS – collaborations

public List getTeamMembers()
{
  return Collections.unmodifiableList(this.teamMembers);
}

protected ListIterator getTeamMemberList()
{
  return this.teamMembers.listIterator();
}

public void addTeamMember( ITeamMember aTeamMember)
throws BusinessRuleException
{
  if (aTeamMember == null)
     throw new BusinessRuleException("Cannot add null team member.");
  this.teamMembers.add(aTeamMember);
}

public void removeTeamMember( ITeamMember aTeamMember)
throws BusinessRuleException
{
  if (aTeamMember == null)
     throw new BusinessRuleException("Cannot remove null team member.");
  this.teamMembers.remove(aTeamMember);
}

Listing 7.9. Java method for printing Person.

// PRINT

public String toString()
{
  StringBuffer buffy = new StringBuffer(60);
  buffy.append("Person: ");
  buffy.append("\nName: " + this.name);
  buffy.append("\nTitle: " + this.title);
  buffy.append("\nEmail: " + this.email);
  return buffy.toString();
}

Equals

Again, following normal Java coding standards, an object checks if it is equal to another object in its equals method, which takes a parameter of type Object. Under the “Most Specific Carries the Loadprinciple (74), the person, as a generic, does not compare its team member collaborator to that of the other person. See Listing 7.10.

Listing 7.10. Java method for checking for equal Persons.

// EQUALS

public boolean equals( Object anObject)
{
  if (anObject instanceof Person)
  {
    Person other = (Person)anObject;
    if (!this.name.equals(other.name)) return false;
    if (!this.email.equals(other.email)) return false;
    if (!this.title.equals(other.title)) return false;
    return true;
  }
  return false; // not instance of Person
}

Run: Test Objects

Run services create objects in the class, pre-loaded with data for testing business services and collaboration rules. We implement services that create objects in the class as static methods (see Listing 7.11).

Listing 7.11. Java test object for Person.

// RUN

public static Person testPerson() throws BusinessRuleException
{
  Person aPerson = new Person("Alfred E. Neuman");
  aPerson.setEmail("al@neuman.com");
  aPerson.setTitle("President");
  return aPerson;
}

Run: Test Case

Once the DIAPER process is complete, a simple test case (see Listing 7.12) ensures the test object works, and exercises some of the accessors with business rules.12 Code that asks an object to perform a service that can potentially throw an exception must be wrapped in try - catch blocks to handle the exceptions.

Listing 7.12. Java code to run test object.

class TestCase
{
  public static void main( String args[])
  {
    Person aPerson = null;

    /** Try to create Person test object.
      * If fail print business rule exception message.
      * If pass print person test object.
    */
    try { aPerson = Person.testPerson(); }
    catch (BusinessRuleException ex)
    {
      System.out.println("BOOM: " + ex.getMessage());
    }
    System.out.println("\n" + aPerson);

    System.out.println("\nPress ENTER to exit");
    try { System.in.read(); }
    catch (java.io.IOException e) { return; }
  } // end main
} // end class TestCase

Person: Squeak DIAPER

This example was implemented in Squeak 3.0.

Define: Person Class

To define the Person class, specify the superclass and the property and collaboration variables. Using the Model dependency mechanism, all our problem domain classes are subclasses of Model (see Listing 7.13).

Listing 7.13. Squeak class definition for Person.

Model subclass: #Person
  instanceVariableNames: 'name title email teamMembers '
  classVariableNames: ''
  poolDictionaries: ''
  category: 'Collaboration-Examples'

Initialize: Person New Methods

Similar to Java constructors, Squeak has class methods for creating objects. These methods must be made to respect the business rule that a person object cannot exist without a name; here is how: (1) override the default object creation method, new, to throw an exception and prevent a person from being created without a valid name; and (2) write a newWith: method for creating a person with a name (see Listing 7.14). In the newWith: method, the inherited new method is invoked to create an uninitialized person object, and the newly created object is asked to initialize itself. As with the Java constructors, the set accessor for the name property throws a business rule exception if the proposed name is bad.

Here is a quick note on our parameter naming conventions: Since Squeak does type checking only at run-time, method arguments should be named to indicate the kind of object expected.13 When an argument is to be used for a particular purpose, such as representing a person’s name or address, then include the purpose in the argument name, too.

Listing 7.14. Squeak methods for creating Persons.

Person class methodsFor: 'instance creation'

new
  BusinessRuleException signal: 'Cannot create person without a name.'

newWith: aNameString
  | aPerson |
  aPerson ← super new initialize.
  aPerson name: aNameString.
  ↑aPerson

Following this convention, property accessors have two-part arguments:

• The first part indicates what the argument represents (“aName”).

• The second part indicates the object type (“String”).

Initialize: Initialize Method

The initialize method sets a person’s properties to default values, which in this case, are empty strings, and sets the collaboration variable to an empty ordered collection (see Listing 7.15). Usually an object only initializes itself once, when it is created.

Listing 7.15. Squeak method for initializing a Person.

Person methodsFor: 'initialize-release'

initialize
  name ← String new.
  title ← String new.
  email ← String new.
  teamMembers ← OrderedCollection new

Accessing: Properties

Using the Model dependency mechanism, each set accessor sends a change notification when the property value changes. For validating email addresses, the email set accessor uses the Squeak library class, MailAddressParser (see Listing 7.16).

Accessing: Collaborations

Collaboration accessors are placed into their own message category to distinguish them from property accessors. As with the Java implementation, these accessors are not yet checking the collaboration rules. See Listing 7.17.

Collaboration accessors follow the same argument naming conventions as property accessors; however, with collaboration accessors, the argument’s type is usually the same as what it represents. For example, a person’s team member collaborator is an object of type TeamMember. So, the argument for a collaboration accessor is named after the type of the collaborator (“aTeamMember”).

Listing 7.16. Squeak property accessors for Person.

Person methodsFor: 'accessing'

email
  ↑email

email: anEmailAddressString
  | aTrimmedEmailAddressString |
  [MailAddressParser addressesIn:
   (aTrimmedEmailAddressString ← anEmailAddressString withBlanksTrimmed)]
   on: Error do: [:ex | BusinessRuleException signal: 'Bad email address.'].
  email ← aTrimmedEmailAddressString.
  self changed: #email

name
  ↑name

name: aNameString
  | aTrimmedNameString |
  (aNameString isNil or:
    [(aTrimmedNameString ← aNameString withBlanksTrimmed) isEmpty])
   ifTrue: [BusinessRuleException signal: 'Person name cannot be nil or empty.'].
  name ← aTrimmedNameString.
  self changed: #name

title
↑title

title: aTitleString
  aTitleString ifNil: [BusinessRuleException signal: 'Person cannot have nil title.'].
  title ← aTitleString withBlanksTrimmed.
  self changed: #title

Listing 7.17. Squeak collaboration accessors for Person.

Person methodsFor: 'collaboration-accessing'

teamMembers
↑teamMembers

addTeamMember: aTeamMember
  aTeamMember
   ifNil: [BusinessRuleException signal: 'Tried to add nil team member.'].
  self teamMembers add: aTeamMember.
  self changed: #teamMembers

removeTeamMember: aTeamMember
  aTeamMember
   ifNil: [BusinessRuleException signal: 'Tried to remove nil team member.'].
  self teamMembers remove: aTeamMember ifAbsent: [ ].
  self changed: #teamMembers

Print

Following normal Squeak coding standards, an object prints its properties and collaborations in its printOn: method. Under the “Most Specific Carries the Loadprinciple (74), the person object, which is a generic, does not print its team member collaborator, which is a specific. See Listing 7.18.

Listing 7.18. Squeak method for printing Person.

Person methodsFor: 'printing'

printOn: aStream
  aStream nextPutAll: 'Person:'.
  aStream cr; nextPutAll: self name.
  aStream cr; nextPutAll: self title.
  aStream cr; nextPutAll: self email

Equals

Squeak allows the = operator to be overridden in any class (see Listing 7.19). This operator enables an object to check if it is equal to another. Under the “Most Specific Carries the Loadprinciple (74), the person, as a generic, does not compare its team member collaborator to that of the other person.

Listing 7.19. Squeak method for checking for equal Persons.

Person methodsFor: 'comparing'

= anObject
  self species = anObject species ifFalse: [↑false].
  self name = anObject name ifFalse: [↑false].
  self title = anObject title ifFalse: [↑false].
  self email = anObject email ifFalse: [↑false].
  ↑true

Run: Test Objects

Many Squeak library classes have examples that create sample objects to assist with learning about the class. In a similar vein, the examples message category contains one or more class methods for building person objects pre-loaded with data. See Listing 7.20.

Run: Inspect It

Squeak has such an interactive environment, programmers often unit test by inspecting the test object and sending it messages from the inspector.

Listing 7.20. Squeak test object for Person.

Person class methodsFor: 'examples'

testPerson
  "Person testPerson"
  | aPerson |
  aPerson ← Person newWith: 'Alfred E. Neuman'.
  aPerson email: 'al@neuman.com'.
  aPerson title: 'President'.
  ↑aPerson

Team Member: Java & Squeak DIAPER

This section applies our coding template for implementing a specific object definition. The class implemented is TeamMember, which is modeled in Figure 7.4, and both the Java and Squeak code are shown in this section.

Team Member Initialization Rules

A team member has the following initialization rules:

• A team member must have a person to exist.

• A team member has its initial security level set to “low.”

• A team member has its initial role set to “member.”

Define: Profile Interface

To specify object inheritance between a team member and its person, the team member profile interface extends the person profile interface. The team member profile interface also describes the get accessors and determine mine services of team members. The implementation of this interface in Java is shown in Listing 7.21.

In part, because Squeak is a late-binding language that does not do compile-time type checking, there is no Squeak equivalent to the Java interface. A Squeak object exhibits its interface at run-time; it either understands a message or it doesn’t.

Define: Conduct Business Interface

The team member conduct business interface extends the team member profile interface and includes the set accessors and conduct business services particular to team members. Because a team member’s privileges are coupled to the value of its role property, special conduct business services—makeAdmin, makeMember, and makeChair—set both properties together. Set accessors for the role property exist, but are not included in the conduct business interface because using them alone could put a team member in an invalid state. The Java code for this interface follows in Listing 7.22.

Listing 7.21. Java profile interface for TeamMember.

public interface ITeamMemberProfile extends IPersonProfile
{
  // ACCESSORS -- get properties
  public SecurityLevel getSecurityLevel();

  // ACCESSORS - get property values
  public boolean isRoleAdmin();
  public boolean isRoleChair();
  public boolean isRoleMember();

  // ACCESSORS -- get collaborators
  public IPerson getPerson();

  // DETERMINE MINE
  public boolean hasNominatePrivilege();
  public boolean hasDeletePrivilege();
}

Again, there is no Squeak equivalent. From this point forward, we will assume the reader knows that interfaces are not used in Squeak, and therefore only the Java code for them will be presented.

Define: TeamMember Class

The TeamMember class has instance variables for its properties and its person collaborator. Both the Java and Squeak code implement a utility class, SecurityLevel, to represent the security levels for team members. Later, we will see that a document also has a security level, and it plays a part in the collaboration rules that govern the document’s nomination process. Since SecurityLevel must be reused among several different domain classes, it is a standalone class and not implemented as an inner class. The complete code for this class is on the CD available with this book.

In Java, the TeamMember class implements the team member conduct business interface (see Listing 7.23). The role property is implemented using an inner class, TeamRole, whose objects have an integer code to efficiently represent, compare, and store the different role values and a string to cleanly print and display the role value14 (see Listing 7.24). Three team role objects, representing the admin, chair, and member role values, are created and stored in static variables within the TeamMember class.15 These are used in the property value accessors defined later.16

Listing 7.22. Java conduct business interface for TeamMember.

public interface ITeamMember extends ITeamMemberProfile
{
  // ACCESSORS -- add collaborators
  public void addPerson( IPerson aPerson ) throws BusinessRuleException;

  // CONDUCT BUSINESS
  public void makeAdmin() throws BusinessRuleException;
  public void makeChair() throws BusinessRuleException;
  public void makeMember() throws BusinessRuleException;
  public void grantNominatePrivilege() throws BusinessRuleException;
  public void grantDeletePrivilege() throws BusinessRuleException;
  public void revokeNominatePrivilege() throws BusinessRuleException;
  public void revokeDeletePrivilege() throws BusinessRuleException;
}

Listing 7.23. Java class specification for TeamMember.

public class TeamMember extends Object implements ITeamMember
{
  // DEFINE
  private IPerson person;
  private byte privileges;
  private TeamRole role;
  private SecurityLevel securityLevel;
}

In Squeak, TeamMember goes into the same class category as Person (see Listing 7.25). The role property is implemented using Squeak association objects. Each association has a symbol key to efficiently represent, compare, and store the different role values and a string to cleanly print and display the role value. Values for the role property are returned by methods in the constants message category (see Listing 7.26).

Listing 7.24. Java inner class definition for TeamRole.

private static class TeamRole
{
  private int code;
  private String role;

  TeamRole(int roleCode, String roleString)
  {
    this.code = roleCode;
    this.role = roleString;
  }
  public int getCode() { return this.code; }
  public String toString(){ return this.role; }
  public boolean equals(Object anObject)
  {
    if (anObject instanceof TeamRole)
    return (this.code == ((TeamRole)anObject).getCode());
    else return false;
  }
}

Listing 7.25. Squeak class definition for TeamMember.

Model subclass: #TeamMember
  instanceVariableNames: 'person team privileges role securityLevel '
  classVariableNames: ''
  poolDictionaries: ''
  category: 'Collaboration-Examples'

Listing 7.26. Squeak constants for TeamMember role.

TeamMember methodsFor: 'constants'

roleAdmin
  ↑Association key: #admin value: 'Admin'

roleChair
  ↑Association key: #chair value: 'Chair'

roleMember
  ↑Association key: #member value: 'Member'

Initialize: TeamMember

Applying the initialization rules and the “Minimum Parameterprinciple (73), TeamMember requires one constructor in Java and one class new method in Squeak; each takes only a single parameter. The following Java and Squeak methods initialize the new object’s properties before making the collaboration. Often, collaboration rules check property values, and if the properties are not yet initialized, then errors can occur.

The default constructor is not implemented in the Java version because the person collaboration is required for the parent – child relationship. The collaboration accessor for adding a person can throw a business rule exception; therefore, the constructor is declared to throw an exception. See Listing 7.27.

Listing 7.27. Java constructor for TeamMember.

public TeamMember(IPerson aPerson) throws BusinessRuleException
{
  this.makeMember();
  this.securityLevel = new SecurityLevel();
  this.addPerson(aPerson);
}

The Squeak version’s default new method is overridden to throw an exception, and a newWith: method requiring a person object provides the only means for creating team member objects (see Listing 7.28).

Accessing: Properties

To support object inheritance TeamMember has get accessors for three of its person’s properties: name, title, and email. Notice there are no set accessors for these properties. Also, the security level cannot be set. Instead, the security level has its own accessors for changing its level.

In Java, the accessors for object inherited properties return the same types as similarly named accessors in the Person class. See Listing 7.29.

Listing 7.28. Squeak methods for creating and initializing TeamMembers.

TeamMember class methodsFor: 'instance creation'

new
  BusinessRuleException signal: 'Cannot create team member without a person.'

newWith: aPerson
  | aTeamMember |
  aTeamMember ← super new initialize.
  aTeamMember addPerson: aPerson.
  ↑aTeamMember

TeamMember methodsFor: 'initialize-release'

initialize
  person ← nil.
  self makeMember.
  securityLevel ← SecurityLevel new

Listing 7.29. Java property accessors for TeamMember.

// ACCESSORS -- get properties

public SecurityLevel getSecurityLevel()
{
  return this.securityLevel;
}

// ACCESSORS -- get inherited properties

public String getName()
{
  return this.person.getName();
}

public String getTitle()
{
  return this.person.getTitle();
}

public String getEmail()
{
  return this.person.getEmail();
}

The Squeak implementations of the accessors for the inherited properties go in the same message category as the team member accessors to emphasis the transparency of these properties (see Listing 7.30).

Listing 7.30. Squeak property accessors for TeamMember.

TeamMember methodsFor: 'accessing'

securityLevel
↑securityLevel

privileges
↑privileges

role
↑role

title
↑self person title

email
↑self person email

name
↑self person name

Accessing: Property Values

Instead of simple accessors for the role and privileges properties, TeamMember has special get and set accessor services to protect values of these properties and encapsulate their implementations. The special get accessors check the properties for particular values, and the special set accessors assign the properties particular values. In both cases, the name of the particular value is encoded in the accessor name.

The naming convention supports the evolution of these properties into full-blown role history objects.17 If that should happen, then all the methods with “role” in their name would be delegated to services in the role history object.18

Also shown here are the conduct business services that ensure that the proper privileges are assigned when the role property is set. Putting that code in the role set property value accessor would be doing too much work. For example, having the setRoleAdmin accessor also changing privileges would be creating a side-effect in addition to setting a property, and programming by side-effects has bad karma. To discourage use of the role property value set accessors, such as setRoleAdmin, we prefix them differently to distinguish them. Our convention for accessors that bypass business rules is to prefix them with “doSet.” Similarly, get accessors that return raw data values, such getPrivileges, are prefixed with “doGet.” For more on why we have these doSet methods and why they are public, see in the following chapter, “Methods for Enforcing Property Rules.”

In the Java code, static variables (shown in all capital letters) contain the possible values for the role and privileges properties 19 (see Listing 7.31).

Listing 7.31. Java property value accessors for TeamMember.

// ACCESSORS - test for property values

public boolean isRoleAdmin()
{
  return this.role.equals(ROLE_ADMIN);
}

public boolean isRoleChair()
{
  return this.role.equals(ROLE_CHAIR);
}

public boolean isRoleMember()
{
  return this.role.equals(ROLE_MEMBER);
}

public boolean hasNominatePrivilege()
{
  return (this.privileges & PRIVILEGES_NOMINATE_MASK) > 0;
}

public boolean hasDeletePrivilege()
{
  return (this.privileges & PRIVILEGES_DELETE_MASK) > 0;
}
// ACCESSORS - set properties to special values

public void doSetRoleAdmin()
{
  this.role = ROLE_ADMIN;
}

public void doSetRoleChair()
{
  this.role = ROLE_CHAIR;
}
public void doSetRoleMember()
{
  this.role = ROLE_MEMBER;
}

// CONDUCT BUSINESS

public void makeMember() throws BusinessRuleException
{
  this.doSetRoleMember();
  this.privileges = PRIVILEGES_DEFAULT_MASK;
}

public void makeAdmin() throws BusinessRuleException
{
  this.doSetRoleAdmin();
  this.grantNominatePrivilege();
  this.revokeDeletePrivilege();
}

public void makeChair() throws BusinessRuleException
{
  this.doSetRoleChair();
  this.grantNominatePrivilege();
  this.grantDeletePrivilege();
}

public void grantNominatePrivilege()
{
  this.privileges |= PRIVILEGES_NOMINATE_MASK;
}

public void grantDeletePrivilege()
{
  this.privileges |= PRIVILEGES_DELETE_MASK;
}

public void revokeNominatePrivilege()
{
  if (this.hasNominatePrivilege())
      this.privileges ^= PRIVILEGES_NOMINATE_MASK;
}


public void revokeDeletePrivilege()
{
  if (this.hasNominatePrivilege())
      this.privileges ^= PRIVILEGES_DELETE_MASK;
}

In the Squeak code, additional instance methods that are not shown here return the possible values for the role and privileges properties 20 (see Listing 7.32).

Listing 7.32. Squeak property value accessors for TeamMember.

TeamMember methodsFor: 'accessing'

isRoleAdmin
↑self doGetRole = self roleAdmin

isRoleChair
↑self doGetRole = self roleChair

isRoleMember
↑self doGetRole = self roleMember

TeamMember methodsFor: 'testing'

hasDeletePrivilege
  ↑self getPrivileges anyMask: self privilegeDeleteMask

hasNominatePrivilege
  ↑self getPrivileges anyMask: self privilegeNominateMask

TeamMember methodsFor: 'domain services'

makeAdmin
  self doSetRoleAdmin.
  self grantNominatePrivilege.
  self revokeDeletePrivilege

makeChair
  self doSetRoleChair.
  self grantNominatePrivilege.
  self grantDeletePrivilege

makeMember
  self doSetRoleMember.
  self doSetPrivileges: self privilegeDefaultMask


grantDeletePrivilege
  self doSetPrivileges: (self doGetPrivileges bitOr: self privilegeDeleteMask)

grantNominatePrivilege
  self doSetPrivileges: (self doGetPrivileges bitOr: self privilegeNominateMask)

revokeDeletePrivilege
  self hasDeletePrivilege
   ifTrue: [self doSetPrivileges: (self doGetPrivileges bitXor: self privilegeDeleteMask)]

revokeNominatePrivilege
  self hasNominatePrivilege
   ifTrue: [self doSetPrivileges: (self doGetPrivileges bitXor: self privilegeNominateMask)]

TeamMember methodsFor: 'private'

doSetRoleAdmin
  role ← self roleAdmin.
  self changed: #role

doSetRoleChair
  role ← self roleChair.
  self changed: #role

doSetRoleMember
  role ← self roleMember.
  self changed: #role

doSetPrivileges: bits
  privileges ← bits.
  self changed: #privileges

Accessing: Collaborations

Since a child cannot exist without its parent object, only the get and set accessors are written for the person collaborator. In Listings 7.33 and 7.34 the code is not checking collaboration rules. Can a team member change its person? No, but we will enforce that along with other business rules when we implement them in the next chapter.

Print

Using the “Most Specific Carries the Loadprinciple (74), a team member has the responsibility of printing its properties and asking its generic collaborations to print it (see Listings 7.35 and 7.36).

Listing 7.33. Java collaboration accessors for TeamMember.

// ACCESSORS – collaborators

public IPerson getPerson()
{
  return this.person;
}

public void addPerson( IPerson aPerson) throws BusinessRuleException
{
  if (aPerson == null)
     throw new BusinessRuleException("Tried to add null person");
  this.person = aPerson;
}

Listing 7.34. Squeak collaboration accessors for TeamMember.

TeamMember methodsFor: 'collaboration-accessing'

person
↑person

addPerson: aPerson
  aPerson
   ifNil: [BusinessRuleException signal: 'Tried to add nil person.'].
  person ← aPerson.
  self changed: #person

Listing 7.35. Java method for printing a team member.

// PRINT

public String toString()
{
  StringBuffer buffy = new StringBuffer(60);
  buffy.append("Team Member: ");
  buffy.append("\nRole: " + this.role);
  buffy.append("\n" + this.securityLevel);
  buffy.append(this.person.toString());
  return buffy.toString();
}

Listing 7.36. Squeak method for printing a team member.

TeamMember methodsFor: 'printing'

printOn: aStream
  aStream nextPutAll: 'TeamMember:'.
  aStream cr; nextPutAll: self doGetRole value.
  self securityLevel printOn: aStream cr.
  self person printOn: aStream cr.
  self team printOn: aStream cr

Equals

Using the “Most Specific Carries the Loadprinciple (74), a specific team member has the responsibility of checking equality for itself and asking its generic collaborator, person, to check itself (see Listings 7.37 and 7.38).

Listing 7.37. Java method for checking for equal TeamMembers.

// EQUALS

public boolean equals (Object anObject)
{
  if (anObject instanceof TeamMember)
    {
      TeamMember other = (TeamMember)anObject;
      if (!this.role.equals(other.role)) return false;
      if (this.person == null && (other.person != null)) return false;
      if (this.person != null && (!this.person.equals(other.person)))
         return false;
    return true;
    }
  else return false; // not instance of TeamMember
}

Listing 7.38. Squeak method for checking for equal TeamMembers.

TeamMember methodsFor: 'comparing'

= anObject
  self species = anObject species ifFalse: [↑ false].
  self doGetRole = anObject doGetRole ifFalse: [↑ false].
  self person = anObject person ifFalse: [↑ false].
  ↑ true

Run: Test Objects

Specific test objects require generic test objects. TeamMember test objects use Person test objects. The following TeamMember test objects are created:

• A team member with chair role status and a high security level

• A team member with admin role status and default (low) security level

• A team member with member role status, default security, and no nominate privileges

• A team member with member role status, nominate privileges, and secret security level

Code for only one test object is shown in Listings 7.39 and 7.40. See the CD for the others.

Listing 7.39. Java test object for TeamMember.

// RUN

public static TeamMember testChair() throws BusinessRuleException
{
  TeamMember aTeamMember = new TeamMember(Person.testPerson());
  aTeamMember.makeChair();
  SecurityLevel sLevel = aTeamMember.getSecurityLevel();
  sLevel.setLevelHigh();
  return aTeamMember;
}

Listing 7.40. Squeak test object for TeamMember.

TeamMember class methodsFor: 'examples'

testChair
  "TeamMember testChair"
  | aTeamMember |
  aTeamMember ← self newWith: Person testPerson and: Team testTeam.
  aTeamMember makeChair.
  aTeamMember securityLevel setLevelHigh.
  ↑aTeamMember

Whole – Part Object Definitions

Collaboration patterns described by the whole – part pattern are:

container – content

assembly – part

group – member

outer place – place

Building whole – part collaborations is very similar to building generic – specific collaborations if the whole is considered like the generic and the part is considered like the specific. In fact, coding the whole – part requires only these few modifications to the generic – specific DIAPER:

• Profile interfaces are unnecessary.

• The whole always allows for multiple parts.

• A part may have multiple wholes (e.g., group – member collaborations).

• A part may exist without a whole.

Putting the work in the specific was a major determinant in coding generic – specific collaborations. Applying that same principle when coding whole – part collaborations impacts these steps in the DIAPER:

image

Unlike the generic – specific collaborations, profile interfaces are unnecessary in whole – part collaborations because whole – part does not use object inheritance. Conduct business interfaces are still recommended when flexibility and extensibility are desired.

Whole – Part Example

This example continues the implementation of the domain we started in the previous section. Here we are implementing the group – member collaboration.

EXAMPLEA team member belongs to exactly one team. Each team has a description and format, which can be one of the following: no chair, single chair, or multiple chairs (see Figure 7.6). The team’s format defines the following collaboration rule: A team member cannot be added to a team if the team member has a chair role and the team’s format cannot support a chair being added to the team.

Figure 7.6. Team – TeamMember object model.

image

In the Team – TeamMember object model, the team member object is playing the specific in a generic – specific collaboration and it is playing the part in a whole – part collaboration.

Team: Java & Squeak DIAPER

In terms of implementation, the interesting aspects of the Team class are the format property and the collection of team member collaborators.

The format property is implemented like the role property of the TeamMember class, by encapsulating its representation inside the class, and providing special property value accessors. The team member collaboration is implemented similarly. The object definition has an instance variable for a collection object and accessors for adding and removing team members. A get accessor returns a view on the collection, and a special get accessor returns a single team member for a person, assuming the person is on the team.

Define: Conduct Business Interface

Determine mine services are included in this conduct business interface because no profile interface is implemented. See Listing 7.41.

Listing 7.41. Java conduct business interface for Team.

public interface ITeam
{
  // ACCESSING - get /set properties
  public String getDescription();
  public void setDescription( String newDescription)
    throws BusinessRuleException;

  // ACCESSING - get property values
  public boolean isFormatMultipleChair();
  public boolean isFormatSingleChair();
  public boolean isFormatNoChair();

  // ACCESSING - set property values
  public void setFormatNoChair() throws BusinessRuleException;
  public void setFormatSingleChair() throws BusinessRuleException;
  public void setFormatMutlipleChair() throws BusinessRuleException;

  // ACCESSING -- collaborations
  public List getTeamMembers();
  public void addTeamMember( ITeamMember aTeamMember)
    throws BusinessRuleException;
  public void removeTeamMember( ITeamMember aTeamMember)
    throws BusinessRuleException;

  // DETERMINE MINE
  public List getChairs();
  public ITeamMember getTeamMember(IPersonProfile aPerson);
}

Define: Team Class

The Team class has an instance variable to hold the collection of team members, plus instance variables for the description and format properties.

For the Java version, a static inner class, TeamFormat, which is not shown here but is in the CD code listings, is used to represent the three format values—no chair, single chair, and multiple chairs (see Listing 7.42).

Listing 7.42. Java class specification for Team.

public class Team extends Object implements ITeam
{
  // DEFINE
  private String description;
  private ArrayList teamMembers;
  private TeamFormat format;
}

Listing 7.43. Squeak class definition for Team.

Model subclass: #Team
  instanceVariableNames: 'description teamMembers format '
  classVariableNames: ''
  poolDictionaries: ''
  category: 'Collaboration-Examples'

Initialize

Team initialization code creates the collection object and sets the format to the default of “multiple chairs.”

In Java, the initialization of the array list object and setting the format to the default occurs in the default constructor. See Listing 7.44. No other constructors are needed. In Squeak this initialization occurs in the initialize method. See Listing 7.45.

Listing 7.44. Java constructor for Team.

public Team()
{
  this.description = new String();
  this.format = FORMAT_MULTIPLE;
  this.teamMembers = new ArrayList();
}

Accessing: Properties

The description instance variable has the usual get and set methods, returning and taking a string value, respectively. The format instance variable has get property value accessors of the form “isFormatX” for each X value of the format, and has set property value accessors of the form “setFormatX” for each X value of the format. Both the Java and Squeak code examples are very straightforward, so the code is not included here. As usual, the code is on the CD.

Listing 7.45. Squeak methods for creating and initializing a team.

Team class methodsFor: 'instance creation'

new
  ↑super new initialize

Team methodsFor: 'initialize-release'

initialize
  description ← String new.
  format ← self setFormatMultipleChair.
  teamMembers ← OrderedCollection new

Accessing: Collaborations

The team has two get accessors for the team member collaboration: one returns a view containing all the team members, and the second returns a team member for a given person.

The Java methods for managing the team member array list are the same as in person. A public method returns an “unmodifiable” version of the array list, and a protected method returns a list iterator on the array list. Methods to establish and dissolve a team member collaboration add and remove the team member from the array list, respectively. These methods are listed on the CD.

What is new in this Java implementation is the method that locates the team member of the team for a given person. Searching a collection for the first element that returns true for a given test is such a routine occurrence that we created our own utility class, CollectionDetector, with a detect method containing code that iterates through a collection. For each element in the collection or list, the detect method calls the detectBlock method with the list element and the value that is the subject of the search. If the detect-Block returns true for any list element, then the search stops and that list element is returned as the result. To use the CollectionDetector, define a subclass implementing the detectBlock method with the code needed to locate the desired object in the list. Next, create an object in the subclass, and ask that object to detect within a given list for a given object. The getTeam-Member method defines the CollectionDetector subclass as an anonymous inner class, and creates an object in this class to detect a person within the list of team members (see Listing 7.46). Code for the CollectionDetector class is included on the CD.

Listing 7.46. Java get TeamMember for person collaboration accessor for Team.

public ITeamMember getTeamMember(IPersonProfile aPerson)
{
  CollectionDetector personDetector = new CollectionDetector()
  {
    public boolean detectBlock(Object listElement, Object keyValue)
    {
      return ((ITeamMember)listElement).getPerson().equals(keyValue);
    }
  }; // end anonymous inner class definition
  return (ITeamMember)personDetector.detect(this.teamMembers, aPerson);
}

In Squeak, collections can be asked to find and return an object that satisfies some test; the message to the collection is detect:ifNone:, and it returns nil if no object within the collection satisfies the test (see Listing 7.47).

Listing 7.47. Squeak get teamMemberFor person collaboration accessor for Team.

Team methodsFor: 'collaboration-accessing'

teamMemberFor: aPerson
  ↑self teamMembers
   detect: [:aTeamMember | aTeamMember person = aPerson]
   ifNone: [ ]

Printing

Using the “Part Carries the Loadprinciple (76), a team does not ask its parts to print. Instead, the team prints only its own properties.

In Java, Team implements the usual toString method to print the description and format properties of a team. In Squeak, Team implements the printOn: method to do the same. Code listings are on the CD.

Equals

Using the “Part Carries the Loadprinciple (76), a team compares only its own properties and does not try to compare its team members to those of the other team.

In Java, the equals method compares the properties of a team. Squeak allows using the = operator for this comparison. Code listings are on the CD.

Run: Test Objects

By default, a team allows multiple chairs, so two other test objects are required, one for each remaining format value. Code is shown for the single chair team test object in Listings 7.48 and 7.49. Code for other test objects is shown on the CD.

Listing 7.48. Java test object for a team with a single chair.

public static Team testSingleChairTeam() throws BusinessRuleException
{
  Team aTeam = new Team();
  aTeam.setFormatSingleChair();
  aTeam.setDescription("Executive Strategy Team");
  return aTeam;
}

Listing 7.49. Squeak test object for a team with a single chair.

Team class methodsFor: 'examples'

testSingleChairTeam
  "Team testSingleChairTeam"
  | aTeam |
  aTeam ← Team new.
  aTeam setFormatSingleChair.
  aTeam description: 'Executive Strategy Team'.
  ↑aTeam

Team Member: Java & Squeak DIAPER (Updated)

This section shows only the parts of the TeamMember implementation that need to be updated to support the TeamMember - Team collaboration.21

Team Member Initialization Rules

Because a team member represents a person’s participation in a particular team, a team member cannot exist without both a person and a team. Team-Member thus requires an additional initialization rule: A team member must have a team to exist.

Define: TeamMember Class

Give TeamMember an instance variable to reference its team collaborator.

Using the “How I See Youprinciple (71), the team instance variable in the Java implementation is typed with the team conduct business interface (see Listing 7.50).

In Squeak, we just add a new instance variable to the TeamMember definition (see Listing 7.51).

Listing 7.50. Java revised class specification for TeamMember.

public class TeamMember extends Object implements ITeamMember
{
  // DEFINE
  private IPerson person;
  private ITeam team;
  private TeamRole role;
  private byte privileges;
  private SecurityLevel securityLevel;
}

Listing 7.51. Squeak revised class definition for TeamMember.

Model subclass: #TeamMember
  instanceVariableNames: 'person team role privileges securityLevel '
  classVariableNames: ''
  poolDictionaries: ''
  category: 'Collaboration-Examples'

Initialize

The new initialization rule changes how team member objects are created. The team member object inherits some of its properties from its person. The team checks collaboration rules for the team member that may include these object inherited properties, so the collaboration between team member and person must be set before the Team – TeamMember collaboration is established.

Object creation methods that establish two or more collaborations need some extra care. While the first collaboration may pass the rules and be established, the second could fail, in which case, the first must be dissolved, too. In this example, the team member must be removed from the person object if the team member cannot be a part of the team.22

The team object is added to the parameter list of the TeamMember Java class constructor, and code is added to break the person collaboration if the team collaboration fails (see Listing 7.52).

Listing 7.52. Java revised constructor for TeamMember.

public TeamMember( IPerson aPerson, ITeam aTeam) throws BusinessRuleException
{
  this.makeMember();
  this.securityLevel = new SecurityLevel();
  this.addPerson(aPerson);
  try {this.addTeam(aTeam);}
  catch(BusinessRuleException anException)
  {
    aPerson.removeTeamMember(this);
    throw anException;
  }
}

The existing Squeak object creation method needs to be converted into a method that takes two parameters: a person and a team. Code is added to dissolve the person collaboration if the team collaboration fails (see Listing 7.53).

Listing 7.53. Squeak revised method for creating a team member.

TeamMember class methodsFor: 'instance creation'

newWith: aPerson and: aTeam
| aTeamMember |
aTeamMember ← super new initialize.
aTeamMember addPerson: aPerson.
[aTeamMember addTeam: aTeam]
  on: BusinessRuleException
  do: [:ex|
   aPerson removeTeamMember: aTeamMember.
   ex signal].
↑aTeamMember

Accessing

The new collaboration requires new accessors to get, add, and remove the collaborator.

The Java accessors are similar to those for the person collaborator, so only the method signatures for the accessors are shown in Listing 7.54.

The Squeak methods in Listing 7.55 are added to the “collaboration-accessing” message category.

Listing 7.54. Java method signatures for team collaborator accessors of TeamMember.

public ITeam getTeam();
public void addTeam( ITeam aTeam) throws BusinessRuleException;
public void removeTeam( ITeam aTeam) throws BusinessRuleException;

Listing 7.55. Squeak method signatures for team collaborator accessors of TeamMember.

team
addTeam: aTeam
removeTeam: aTeam

Printing

Applying the “Part Carries the Loadprinciple (76), a team member prints its own details, and asks its team to print its own details as well. Code should be added to the Java toString method and the Squeak printOn: method to print the team. These methods are on the CD.

Equals

Applying the “Part Carries the Loadprinciple (76), a team member compares its team to that of another team member. The Java equals method and the Squeak = operator are modified to compare the teams. See Listings 7.56 and 7.57.

Listing 7.56. Java revised method for checking for equal TeamMembers.

public boolean equals(Object anObject)
{
  if (anObject instanceof TeamMember)
  {
    TeamMember other = (TeamMember)anObject;
    if (!this.role.equals(other.role)) return false;
    if (this.person == null && (other.person != null)) return false;
    if (this.person != null && (!this.person.equals(other.person)))
       return false;
    if (this.team == null && (other.team != null)) return false;
    if (this.team != null && (!this.team.equals(other.team)))
       return false;
    return true;
  }
  else return false; // not instance of TeamMember
}

Listing 7.57. Squeak revised method for checking for equal TeamMembers.

TeamMember methodsFor: 'comparing'

= anObject
  self species = anObject species ifFalse: [↑false].
  self getRole = anObject getRole ifFalse: [↑false].
  self person = anObject person ifFalse: [↑false].
  self team = anObject team ifFalse: [↑false].
  ↑true

Run: Test Objects

Applying the “Part Carries the Loadprinciple (76), a team member test object uses the team test object. The purpose of the test object is to test the collaboration, not to create a team filled with lots of team members for extensive testing. Tests of that magnitude require more sophisticated test techniques and test harnesses. For our purposes, we need to update all the test methods in both TeamMember classes to create team members with the new constructor and using the person and team test objects. See Listings 7.58 and 7.59.

Listing 7.58. Java test object for TeamMember as chair of team.

public static TeamMember testChair() throws BusinessRuleException
{
  ITeam aTeam = Team.testTeam();
  TeamMember aTeamMember = new TeamMember(Person.testPerson(),aTeam)
  aTeamMember.makeChair();
  SecurityLevel sLevel = aTeamMember.getSecurityLevel();
  sLevel.setLevelHigh();
  return aTeamMember;
}

Listing 7.59. Squeak test object for TeamMember as chair of team.

TeamMember class methodsFor: 'examples'

testChair
  "TeamMember testChair"
  | aTeamMember |
  aTeamMember ← self newWith: Person testPerson and: Team testTeam.
  aTeamMember makeChair.
  aTeamMember securityLevel setLevelHigh.
  ↑ aTeamMember

Transaction – Specific Object Definitions

Collaboration patterns described by the transaction – specific pattern are:

transaction – role

transaction – specific item

transaction – place

follow-up transaction – transaction

line item – specific item

Transaction - Specific vs. Generic – Specific

Transaction – specific collaborations look deceptively like generic – specific collaborations; however, a transaction is a more active collaborator than a generic. A single transaction often unites multiple collaboration patterns (see Figure 7.7). Because transactions coordinate between many collaboration patterns, they are called the “glue” of the object model.

Figure 7.7. A transaction coordinates between many collaboration patterns.

image

Transaction - Specific Implementation Guidelines

Here are the important facts about transaction – specific collaborations that shape how they are implemented:

• No object inheritance is involved.

• No collaborator is more “specific” than another.

• A transaction can have multiple specifics, but each is of a different type.

• A specific can have multiple transactions of the same type.

• A specific can have different types of transactions.

As with whole – part, profile interfaces are not necessary; conduct business interfaces are always recommended.

The specific work principle does not apply with transaction – specific. Instead, the relevant rule of thumb arises from the fact that a transaction exists to record the interaction of a person and a thing at a given place. This implies that a transaction cannot exist without its specifics, namely its role, specific item, and place.23 The reverse is clearly not true because a person, place, and thing can exist independently of any transactions.

Read that principle carefully. It does not say to put all the work into a central controller object. Each object is still responsible for performing its own share of the work, but only when cued from the coordinator.

Here is how the directing coordinator principle applies to the transaction – specific DIAPER:

image

A transaction brings other objects, specifics, together. The specifics are not usually of the same type. Rather than forcing the different types into one collection of specifics, the recommended approach is to define a separate instance variable for each type.

Multiple transactions for a specific record that object’s history of interactions. Also, analyze transactions services do work across the transactions of a specific. The natural implementation defines an instance variable within the specific to hold the transactions.

Transaction – Specific Example

This example illustrates the transaction – role and transaction – specific item collaborations.

EXAMPLEA team member can nominate documents for publication on the corporate Web site. Using object think, a document nominates itself for a team member. The document nominate service creates a nomination (transaction) with the team member (role) and itself (specific item).24 See Figure 7.8.

Figure 7.8. TeamMember – Nomination– Document object model.

image

Team member collaboration rules:

• A team member must have nominate privileges to nominate a document.

A team member can only nominate a fixed number of times per 30 days:

• Five times for a team member whose role is member or admin.

• Ten times for a team member whose role is chair.

Document collaboration rules:

• A document must have a title to be nominated.

• A document cannot be nominated after it is published.

• A document cannot be nominated while it has a pending nomination.

• A document cannot be nominated by a team member with a security level less than the document’s security level.

With the addition of TeamMember – Nomination – Document to our object model, the team member object is now playing in three collaboration patterns. It is playing as the specific in a generic – specific collaboration, as the part in a whole – part collaboration, and as a specific in a transaction – specific collaboration.

Document: Java & Squeak DIAPER

Interesting implementation aspects of the document are the security level, publication date properties, and the collection of nominations. We have seen the security level before. This is the same security level object used in the TeamMember class implementation. Every document is created with an initial security level of “low.”

Publication date is only set if the document is published; otherwise, the date is null. When the state of the object determines whether a property exists or is accessible, it is a business rule violation to request the property when the object is not in the proper state. Accordingly, asking an unpublished document for its publication date is a business rule violation, and so the get accessor throws an exception.

Use a collection to hold collaborators. This collection will be searched to find the approved nomination and to tally the number of nominations within a range of dates. A document has an analyze transactions service to return its approved nomination, because while the document may have any number of pending, in review, and rejected nominations, it can only have one approved nomination. Also, once a document has an approved nomination, its behavior changes. However, asking an unapproved document for its approved nomination is similar to asking an unpublished document for its publication date; it generates a business rule violation.

A document has the following initialization rules:

• A document must have a title property to exist.

• A document by default has its security level set to “low.”

Define: Conduct Business Interface

This interface is fairly straightforward. Because there is no profile interface, the conduct business interface has the accessors, determine mine services, and analyze transactions service. It also provides the conduct business services to publish and nominate a document. See Listing 7.60.

Listing 7.60. Java conduct business interface for Document.

public interface IDocument
{
  // ACCESSORS -- get properties
  public String getTitle();
  public SecurityLevel getSecurityLevel();
  public Date getPublicationDate() throws BusinessRuleException;

  // ACCESSORS -- get collaborators
  public List getNominations();


  // ACCESSORS -- set properties
  public void setTitle(String newTitle) throws BusinessRuleException;

  // DETERMINE MINE
  public boolean isPublished();
  public boolean isApproved();

  // ANALYZE TRANSACTIONS
  public INomination getApprovedNomination()
  throws BusinessRuleException;

  // CONDUCT BUSINESS
  public void publish() throws BusinessRuleException;
  public void nominate(ITeamMember aTeamMember)
  throws BusinessRuleException;
}

Define: Document Class

The Java code keeps the nominations sorted by storing them in a TreeSet, which is a standard Java class that exhibits the SortedSet interface. A TreeSet sorts itself in ascending order with the lowest ordered elements first.

Listing 7.61. Java class specification for Document.

public class Document extends Object implements IDocument
{
  private String title;
  private Date publicationDate;
  private SecurityLevel securityLevel;
  private TreeSet nominations;
}

Listing 7.62. Squeak class definition for Document.

Modelsubclass: #Document
  instanceVariableNames: 'title publicationDate securityLevel nominations '
  classVariableNames: ''
  poolDictionaries: ''
  category: 'Collaboration-Examples'

Initialize

Document initialization code creates the collection object, sets the security level to the default, sets the string property to an empty string, and sets the publication date to null. A string title is required for the document to exist, and the set title accessor throws an exception if the title is null. See Listings 7.63 and 7.64.

Listing 7.63. Java constructor for Document.

public Document( String newTitle) throws BusinessRuleException
{
  this.setTitle(newTitle);
  this.publicationDate = null;
  this.securityLevel = new SecurityLevel();
  this.nominations = new TreeSet();
}

Listing 7.64. Squeak methods for creating and initializing a document.

Document class methodsFor: 'instance creation'

new
  BusinessRuleException signal: 'Cannot create document without a title.'

newWith:aTitleString
  | aDocument |
  aDocument ← super new initialize.
  aDocument title: aTitleString.
  ↑aDocument

Document methodsFor: 'initialize-release'

initialize
  title ← String new.
  publicationDate ← nil.
  securityLevel ← SecurityLevel new.
  nominations ← OrderedCollection new

Accessing: Properties

The usual suspects apply here. The publication date has some interesting accessors. The get accessor throws a business exception if the property is not set. To avoid this unhappy event, an additional property value accessor tests if the publication date is set. These accessors are shown in Listings 7.65 and 7.66; the others are on the CD.

Listing 7.65. Java publicationDate property accessors for Document.

public Date getPublicationDate() throws BusinessRuleException
{
  if (this.publicationDate == null)
    throw new BusinessRuleException("Document is unpublished.");
  else return this.publicationDate;
}

public boolean isPublished()
{
  try {this.getPublicationDate();}
  catch(BusinessRuleException ex){return false;}
  return true;
}

Listing 7.66. Squeak publicationDate property accessors for Document.

Document methodsFor: 'accessing'

publicationDate
  publicationDate ifNil: [BusinessRuleException signal: 'Document is unpublished.'].
  ↑publicationDate

Document methodsFor: 'testing'

isPublished
  ↑publicationDate notNil

Accessing: Collaborations

A document’s nominations help determine its state. A document is “approved” for publication when it has an approved nomination. Along with the usual collaboration accessors, there is a service to return the approved nomination. This service raises an exception if the document lacks an approved nomination. To guard against this exception, the document includes an “is approved” service that checks whether the document has an approved nomination without raising an exception.

For the Java version, four collaboration accessors are needed: (1) a public accessor to return an “unmodifiable” list of nominations, (2) a protected accessor to return a list iterator for the nominations, (3) a public method to return the approved nomination, and (4) a public method to ask if a document is approved. The first two accessors are left to the reader. The “get approved nomination” method locates an approved nomination in the list of nominations by using an anonymous inner class programmed to detect an approved nomination. This technique was explained earlier (see Listing 7.46). The “is approved” method provides a check before trying to get the approved nomination (see Listing 7.67).

The Squeak version requires similar accessors as shown in Listing 7.68.

Listing 7.67. Java collaboration accessors for the approved nomination of a document.

public INomination getApprovedNomination() throws BusinessRuleException
{
  INomination aNomination = null;
  CollectionDetector approvedDetector = new CollectionDetector()
  {
    public boolean detectBlock(Object listElement, Object keyValue)
    {
      return ((INomination)listElement).isStatusApproved();
    }
  };
  aNomination =(INomination)approvedDetector.detect(this.nominations);
  if (aNomination == null)
    throw new BusinessRuleException("No approved nomination.");
  return aNomination;
}

public boolean isApproved()
{
  try { this.getApprovedNomination(); }
  catch(BusinessRuleException ex) { return false; }
  return true;
}

Listing 7.68. Squeak collaboration accessors for the approved nomination of a document.

Document methodsFor: 'collaboration-accessing'

approvedNomination
  ↑ self nominations
    detect: [:aNomination | aNomination isStatusApproved]
    ifNone: [BusinessRuleException signal: 'Document has no approved nomination.']

Document methodsFor: 'testing'

isApproved
  ↑self nominations anySatisfy: [:aNomination | aNomination isStatusApproved]

Finishing Up the DIAPER

Printing and comparison (equals) are implemented using the “Let the Coordinator Directprinciple (78): the document prints and compares its own properties, but not those of its nominations. See the CD for the Java and Squeak code.

For testing, we want to be thorough. We will create at least two document test objects: one with a default security level, and another with a secret security level. Only the secret document test object is shown in Listings 7.69 and 7.70.

Listing 7.69. Java test object for a document with secret security.

public static Document testSecret() throws BusinessRuleException
{
  Document aDocument =
       new Document("Food and Beverage Industry Surveillance Tips" );
  SecurityLevel sLevel = aDocument.getSecurityLevel();
  sLevel.setLevelSecret();
  return aDocument
}

Listing 7.70. Squeak test object for a document with secret security.

Document class methodsFor: 'examples'

testSecret
  "Document testSecret"
  | aDocument |
  aDocument ← self newWith: 'Food and Beverage Industry Surveillance Tips'.
  aDocument securityLevel setLevelSecret.
  ↑aDocument

Nomination: Java & Squeak DIAPER

A nomination goes through various lifecycle states:

• Pending

• In review

• Approved

• Rejected

A nomination’s lifecycle state transitions are governed by business rules. A nomination cannot:

• Transition to the “pending” state unless in review or already pending

• Transition to the “in review” state unless pending or already in review

• Transition to the “approved” state unless in review or already approved

• Transition to the “rejected” state unless in review or already rejected

These rules are enforced in the property value accessor methods, but we will defer rule checking until the next chapter.

The nomination has the following initialization rules:

• A nomination must have a document to exist.

• A nomination must have a team member to exist.

• A nomination has a nomination date set to the date and time when the nomination was created.

Define: Conduct Business Interface

To allow sorting of nominations by date in Java, a nomination must exhibit the Comparable interface as shown in Listing 7.71.

Listing 7.71. Java conduct business interface for Nomination.

public interface INomination extends Comparable
{
//ACCESSORS -- get properties
public String getComments();
public Date getNominationDate();

//ACCESSORS -- get property values
public boolean isStatusApproved();
public boolean isStatusRejected();
public boolean isStatusPending();
public boolean isStatusInReview();

//ACCESSORS -- set properties
public void setComments(String newComments)
  throws BusinessRuleException;


//ACCESSORS -- set property values
public void setStatusPending() throws BusinessRuleException;
public void setStatusInReview() throws BusinessRuleException;
public void setStatusApproved() throws BusinessRuleException;
public void setStatusRejected() throws BusinessRuleException;

//ACCESSORS -- get collaborators
public IDocument getDocument();
public ITeamMember getTeamMember();

  // ACCESSORS -- add collaborators
public void addTeamMember(ITeamMember aTeamMember)
  throws BusinessRuleException;
public void addDocument(IDocument aDocument)
  throws BusinessRuleException;

//DETERMINE MINE
public boolean isBefore(Date aDate);
public boolean isAfter(Date aDate);
}

Define: Nomination Class

In Java, a Date object is used to represent the nomination date property, and an inner class is defined to encapsulate the representation of the status property.25 Both collaborator variables, document and team member, are referenced by their conduct business interfaces (see Listing 7.72).

Listing 7.72. Java class specification for Nomination.

public class Nomination extends Object implements INomination
{
  // DEFINE
  private NominationStatus status;
  private Date nominationDate;
  private String comments;
  private IDocument document;
  private ITeamMember teamMember;
}

A Date object is also used in Squeak to represent the nomination date property, and an association key-value pair is used to represent the status property (see Listing 7.73).

Listing 7.73. Squeak class definition for Nomination.

Model subclass: #Nomination
  instanceVariableNames: 'status nominationDate comments document teamMember '
  classVariableNames: ''
  poolDictionaries: ''
  category: 'Collaboration-Examples'

Initialize

A nomination requires two objects to exist. The implementation pattern here is similar to creating team members: if the second collaboration fails, then the first one must be dissolved.

In Java, there is no default constructor, and in Squeak, we override the default new operation to signal an exception. See Listings 7.74 and 7.75.

Listing 7.74. Java constructor for Nomination.

public Nomination( IDocument aDocument, ITeamMember aTeamMember)
                 throws BusinessRuleException
{
  this.status = STATUS_PENDING;
  this.nominationDate = new Date();
  this.comments = new String();
  this.addTeamMember(aTeamMember);
  try { this.addDocument(aDocument); }
  catch(BusinessRuleException ex)
  {
    aTeamMember.doRemoveNomination(this);
    throw ex;
  }
}

Accessing: Properties and Collaborations

Accessors include the regular get accessors, and a set accessor for the comments property. The nomination date cannot change, so it does not have a set accessor. There are also special property value accessors for the status, isStatusApproved, setStatusApproved, etc. These follow the conventions used to define the property value accessors for the team member role property.26 One difference is that these set property value accessors check the state transition rules and throw an exception if any rule is violated. Since these tests are implemented again with the rule checking in the next chapter, we omit the listings here.

Listing 7.75. Squeak methods for creating and initializing a nomination.

Nomination class methodsFor: 'instance creation'

new
  BusinessRuleException
   signal: 'Cannot create nomination without a team member and a document.'

newWith: aTeamMember and: aDocument
  | aNomination |
  aNomination ←super new initialize.
  aNomination addTeamMember: aTeamMember.
  [aNomination addDocument: aDocument]
   on: BusinessRuleException
   do: [:ex|
    aTeamMember doRemoveNomination: aNomination.
    ex signal].
  ↑aNomination

Nomination methodsFor: 'initialize-release'

initialize
  status ←self statusPending.
  nominationDate ←Date today.
  comments ←String new.
  document ←nil.
  teamMember ←nil

For the team member accessor, only add accessors are needed because a nomination cannot exist without its team member and document. Nothing is different here from past collaboration accessors.

Printing

Applying the “Let the Coordinator Directprinciple (78), a nomination prints its own properties, and also directs its collaborators to print themselves. This is implemented in Java using the DateFormat class to format the nomination date for printing (see Listing 7.76).

In Squeak, instead of getting the printString of the collaborators, a slightly more efficient approach is to ask each collaborator to print on the same stream (see Listing 7.77).

Listing 7.76. Java method for printing a nomination.

public String toString()
{
  DateFormat aDateFormat = DateFormat.getDateTimeInstance();
  StringBuffer buffy = new StringBuffer(30);
  buffy.append("Nomination on: ");
  buffy.append(aDateFormat.format(this.nominationDate));
  buffy.append("\nStatus: " + this.status);
  buffy.append("\n" + this.document);
  buffy.append("\n" + this.teamMember);
  return buffy.toString();
}

Listing 7.77. Squeak method for printing a nomination.

Nomination methodsFor: 'printing'

printOn: aStream
  aStream nextPutAll: 'Nomination:'.
  self nominationDate printOn: aStream cr.
  aStream cr; nextPutAll: self doGetStatus value.
  self teamMember printOn: aStream cr.
  self document printOn: aStream cr

Equals

Applying the “Let the Coordinator Directprinciple (78), a nomination compares its own properties, plus it directs its collaborators to compare themselves.

In Java, along with the equals method, a nomination has compareTo methods to support sorting by Java library classes. The compareTo method returns –1, 0, or 1 according to whether the receiver is more recent, at the same date and time, or after the other nomination, respectively. This implementation ensures nominations are sorted with the most recent first. The compareTo methods are shown in Listing 7.78; the equals method is listed on the CD.

The Squeak code is on the CD.

Run: Test Objects

As the coordinating collaborator, the nomination test object uses the other collaborator test objects. See Listings 7.79 and 7.80.

Listing 7.78. Java methods for comparing Nominations.

public int compareTo( Object anObject)
{
  return this.compareTo((Nomination)anObject);
}

public int compareTo( Nomination aNomination)
{
  return aNomination.nominationDate.compareTo(this.nominationDate);
}

Listing 7.79. Java Nomination test object.

public static Nomination testNomination() throws BusinessRuleException
{
  ITeamMember aTeamMember = TeamMember.testTeamMember();
  Nomination aNomination =
    new Nomination(Document.testDocument(), aTeamMember);
  return aNomination;
}

Listing 7.80. Squeak Nomination test object.

Nomination class methodsFor: 'examples'

testNomination
  "Nomination testNomination"
  | aNomination |
  aNomination ←self newWith: TeamMember testChair and: Document testNormal.
  ↑aNomination

Team Member: Java & Squeak DIAPER (Update II)

The TeamMember class needs the following definition updates to collaborate with nomination objects:

• Add a variable to the TeamMember class for holding the collection of nominations.

• Add to the team member profile interface a collaboration accessor for the team member’s nominations.

• Add to the team member conduct business interface collaboration accessors to add and remove a nomination.

We will also update the constructor or initialize methods to create a collection object for the nominations when a team member is created.

Accessors must be added to get all nominations, add a single nomination, and remove a particular nomination.

Because TeamMember is not the coordinator (Principle 78 again), no changes are needed to allow for printing, comparing, or testing.

What’s Next?

The next chapter completes the code started in this chapter by showing how to implement the business rules for the properties and collaborations.