Naked Objects
The anatomy of a naked objectFields | |
When the framework's viewing mechanism portrays a business object
in one of its opened views (most commonly the 'form' view), it looks to
see if the object has any publicly available accessor methods that
return an object of the Fields can be divided into three categories: values, associations, and multiple associations. These can all be seen in the screenshot below, where the values appear as characters on a line and are editable, and the associations show up as icons. For a multiple association, the field can show more than one icon. The label, or field name, is generated from the name of the accessor method (it is the method name without its 'get' prefix, and with spaces added where a capital letter is found). An accessor method often shares the same name as the variable it accesses, but not always. Remember that it is the method name and not the variable name that is used by the framework to label the field displayed to the user. ValuesSimple unshared data values are restricted to classes of the type
Each value object must be declared and initialized, and must be made available through an accessor method: public class Customer extends AbstractNakedObject { private final TextString lastName; private final TextString firstName; public Customer() { firstName = new TextString(); lastName = new TextString(); } public TextString getLastName() { return lastName; } public TextString getFirstName() { return firstName; } } The Since they are marked as final, these variables must be initialized before the containing object can be used. This must be done either after the declaration or, as we have shown, in a suitable constructor. All value object classes have a zero parameter constructor, so they can be instantiated without you having to provide an initial value. As all fields containing a value object are marked as final, only
a Value fields can be made read-only, so the field cannot be
edited. This is indicated to the user by the removal of the light
grey line underneath the field's text. The field can be set at any
time by a call to the public Booking() { reference = new TextString(); reference.setAbout(FieldAbout.READ_ONLY); status = new TextString(); status.setAbout(FieldAbout.READ_ONLY); } One-to-one associationsA one-to-one association is where one business object has a field
that contains a reference to another business object. (A business
object is an object of the type
public class Booking extends AbstractNakedObject { private Location dropOff; public Location getDropOff() { resolve(dropOff); return dropOff; } public void setDropOff(Location newDropOff) { dropOff = newDropOff; objectChanged(); } } In the This is best explained with an example. In the image below the left hand side (with the blue background) shows the user's view. The right hand side (grey background) is a portrayal of what is happening in working memory: In this first screen, the user has retrieved the #2
Confirmed booking from storage and displayed it as an icon.
On the right-hand side we can see that a booking object has been
instantiated in memory, and that the value fields, which are an
integral part of the object, have been retrieved from storage. In this
case, those value fields provide sufficient information for the booking
object's title to be generated (specifically from the
The user now opens up the booking object to show the form view. All value fields and all associated objects are now displayed: In order to display the titles of each of the associated objects (such as the customer for that booking) the framework has automatically resolved those objects. (The booking object still exists in memory, but this is not shown for reasons of space). Each of these objects has now had each of its value fields retrieved from storage, but where the objects contain references (associations) to other objects, these have been instantiated, but not themselves resolved. If the associated object has already been retrieved then that reference is used. In our example this has happened with the Home telephone, the first two locations and the credit card because they have all been used by the booking. The second telephone and the third location have not been used yet and therefore exist only as skeletal objects. The user now opens up a form view of the customer object, in this case as a new window: On the right hand side we can see that this object has now been resolved in memory. Most of the objects that it refers to are already available as they have been used previously, except for the Office telephone and CSC Office location which now must be read in. Note that the location object has a title that uses both the Known As value field and the City association field. The generation of the title therefore forces the automatic retrieval of the New York object. In consequence, accessors for fields that reference other business
objects (such as the In the It is common to control association fields so they can be made inaccessible or read-only, or so that they will only accept certain objects. When an association is marked as inaccessible the field in which it would be normally shown will not show up in the view. If the field is read-only it will not allow objects (of its type) to be dropped into it, flashing red if the user attempts to do so. An association field is controlled through an
public About aboutDropOff(Location location) { return FieldAbout.READ_ONLY; } In addition to the control that is exerted over the accessor
method, the One-to-many associationsA one-to-many association is where a field contains references to
a number of other business objects. This is achieved by specifying
the field as containing an
The following example, taken from the public class Customer extends AbstractNakedObject { private final InternalCollection locations; private final InternalCollection phoneNumbers; public Customer() { locations = new InternalCollection(Location.class, this); phoneNumbers = new InternalCollection(Telephone.class, this); } public final InternalCollection getLocations() { return locations; } public final InternalCollection getPhoneNumbers() { return phoneNumbers; } } The
As the collection is a composite part of the customer object it
is created when the customer is created. The
As with the value objects, the framework takes care of managing
the collections. Whenever the user adds an object to a field that
uses an One-to-many associations can also be made inaccessible or read-only, in the same way as one-to-one associations. Additional control over the adding and removing of specific objects from the collection will be added to the framework in the near future. When an association is marked as inaccessible the field in which it would be normally shown will not be added to the view. When marked as read-only a field will not allow objects (even of the correct type) to be dropped into it; the grey 'hole' on the screen that normally shows where an object can be dropped will not be there. Controlling a one-to-many association field is done through an
public About aboutLocations() { return FieldAbout.READ_ONLY; } Bidirectional associationsSo far we have considered how one object knows about another object - a one-way association. Often this is enough, but sometimes the object that is being referenced also needs to know about the object that is referencing it. Take, for example, an ordering system. An order object will typically hold a reference to the customer who placed the order. However, it may also be useful to be able to get directly from a customer object to his current order, perhaps even to all his orders. To allow this, the customer needs to keep a reference to the order object. This can be achieved by adding a field to the customer class, containing either a single order object, or a collection of orders. Keeping references within both objects in a relationship means that the user can find all of the related objects when given either of the objects as a starting point. That is, the association between the objects is bidirectional. In principle this is simple to implement: you define the fields and suitable accessor methods in both objects, each holding reference to objects of the other type. However, in order to specify such a relationship the user would first have to drop one object into the appropriate field in a second object, and then drop the second object back into the corresponding field in the first object. This would be tedious and prone to error. The obvious solution
would be to modify the The Naked Objects framework simplifies all this through the
optional use of a pair of The relationship between the public class Customer extends AbstractNakedObject { private final InternalCollection bookings; public Customer() { bookings = new InternalCollection(Booking.class, this); } public final InternalCollection getBookings() { return bookings; } public void associateBookings(Booking booking) { getBookings().add(booking); booking.setCustomer(this); } public void dissociateBookings(Booking booking) { getBookings().remove(booking); booking.setCustomer(null); } } With the two association methods in place, dropping a
Booking object onto the grey hole on the
Bookings field within a Customer object
will cause the When the The dissociate method is used to reverse this by removing the
booking from the collection and setting the booking's customer field
to We probably also want to be able to initiate this bi-directional association from the other object - that is, when a Customer object is dropped onto the Customer field within the Booking, then the customer field is set up with that customer object, and the Booking object adds itself to the set of bookings for that customer. The following code shows the associate and dissociate methods
that we must add to the public class Booking extends AbstractNakedObject { private Customer customer; public void associateCustomer(Customer customer) { customer.associateBookings(this); } public void dissociateCustomer(Customer customer) { customer.dissociateBookings(this); } public Customer getCustomer() { resolve(customer); return customer; } public void setCustomer(Customer newCustomer) { customer = newCustomer; objectChanged(); } } Now dropping a Customer object onto the grey
hole in the Customer field within a
Booking object will cause the
Bidirectional associations are coded in the same way whether they are one-to-one, one-to-many, or many-to-many. All that differs is whether we are adding or removing elements from a collection or a single object. The prefixes 'add' and 'remove' can be used instead of the
prefixes 'associate' and 'dissociate'. Methods using these prefixes
read more easily when used with an internal collection. For example
the public class Customer extends AbstractNakedObject { private final InternalCollection bookings; public Customer() { bookings = new InternalCollection(Booking.class, this); } public void addBookings(Booking booking) { getBookings().add(booking); booking.setCustomer(this); } public void removeBookings(Booking booking) { getBookings().remove(booking); booking.setCustomer(null); } public final InternalCollection getBookings() { return bookings; } } The public void associateDropOff(Location newDropOff) { setDropOff(newDropOff); setCity(newDropOff.getCity()); } public void associatePickUp(Location newPickUp) { setPickUp(newPickUp); setCity(newPickUp.getCity()); } Derived fieldsIf a field needs to be derived dynamically from other fields within the object then it is better to use the prefix 'derive' instead of 'get'. This will make the field read-only and non-persistent, avoiding any potential problems that could arise from the persistence mechanism attempting to store or retrieve that field from storage. In the following example a 'due' date is calculated as being 14
days after the date held by the public Date deriveDue() { Date due = new Date(ordered); due.add(14,0,0); return due; } Ordering fieldsThe ordering of the fields when an object is displayed is based on
the array of accessor methods that is produced by Java's reflection
mechanism. This means the ordering depends on how the JVM you
are running collects the data about your class, and you have
no direct contol over this. You may, however, specify an order within
your class definition, which the viewing mechanism then
interprets. This is done by adding a static method called
The following example from the public static String fieldOrder() { return "reference, status, customer, date, time, pick up, drop off, payment method"; } Any listed name that does not match a field is simply ignored, and fields that are not listed will be placed after all the specified fields.
|
|
Copyright (c) 2002 nakedobjects.org You may print this document for your own personal use, or you may copy it in electronic form for access within your organisation, provided this notice is preserved. |