The Source for Java Technology Collaboration
User: Password:



   

Introduction To Naked Objects

by Brian Coyner
07/15/2003

Unzip! Unsnap! AHHHH!! Our business object is naked. It is time to strip applications of complex UIs and give users direct access to the business objects. The concept is simple: write behaviorally complete business model objects and use generic views and controllers. Thus, if a business model object supports a public behavior, then the user has access to that behavior. So why do we need naked objects?

Chances are you have struggled to truly understand the Model-View-Controller (MVC) pattern. Why is this? The concepts are easy, right? The Model contains the core business logic; the View is responsible for displaying a given model's data; the Controller controls the interactions between the model and the view, typically through event notification. Of course, anyone who has worked with Swing knows that the View and Controller are often combined in the same component. For example, a JTable has a model, but is also both the view and controller. Business logic can end up in all three layers, too, which violates the DRY principle (Don't Repeat Yourself).

Our Simple Naked Objects Project

We are going to build a simple address book using the Naked Objects framework. An address book works nicely because it fits into the naked objects philosophy and, of course, it is easy to understand. You may download the example application here. Our first requirement is that our address book may contain zero or more people. Here is the first iteration of our naked Person:

package com.briancoyner.naked.addressbook;

import org.nakedobjects.object.AbstractNakedObject;

public class Person extends AbstractNakedObject {

   // You must implement this method, which comes from the base class, before
   // the class compiles. We'll talk more about this in a bit.
   
   public Title title() {
      return null;
   }
}

The Naked Objects framework requires that all naked objects implement the NakedObject interface. To keep things simple, we can extend from AbstractNakedObject. The AbstractNakedObject provides the base functionality that makes up a naked object, allowing us to focus on the business problem, which is creating a simple address book. Any object that is "naked" can be seen by the user. We will see how to expose objects to an application later in the article. Now that our Person is naked, we need to add a few attributes and provide a way to modify them. This is accomplished using strict naming conventions and specific Naked Objects framework classes. Let's add a few attributes: first name, last name, and birthdate.

package com.briancoyner.naked.addressbook;

import org.nakedobjects.object.AbstractNakedObject;
import org.nakedobjects.object.value.Date;
import org.nakedobjects.object.value.TextString;

public class Person extends AbstractNakedObject {

   private final TextString firstName;
   private final TextString lastName;
   private final Date birthdate;

   public Person() {
      firstName = new TextString();
      lastName = new TextString();
      birthdate = new Date();
   }

   public final TextString getFirstName() {
      return firstName;
   }

   public final TextString getLastName() {
      return lastName;
   }

   public final Date getBirthdate() {
      return birthdate;
   }

   public Title title() {
      return null;
   }
}

Naked Objects Attributes

Our Person object now has a first name, last name, and birthdate. This is plenty for now. You should have noticed that we did not use a java.lang.String or StringBuffer. Instead, we used an org.nakedobjects.object.value.TextString. TextStrings are mutable objects used by the Naked Objects framework to manipulate string values and tell the framework to create a text field for the view (we will see this in a bit). Also, it is considered good form to mark attributes, whose reference never changes, as final.

Naked Object Methods

Each mutable attribute requires that you provide a corresponding "getter" method. The framework locates all "getters" using reflection, and based on the return type, builds the correct UI component. The first and last names are simple text fields, and the birthdate is a text field with date-parsing behavior. Labels are generated automatically, too, by stripping the "get" from each "getter" method and putting a space between characters that differ in capitalization. You may have noticed that Person does not contain "setter" methods. The reason is simple: TextStrings are mutable, so there is no reason to change the instance. There are times when a "setter" is appropriate. For example, a person can only wear one pair of shoes at time. One day they may wear tennis shoes, the next day, sandals. A "setter", in this example, is needed to change a person's shoes. In addition to "getters" and "setters", there are numerous methods that the framework looks for, using reflection. We will examine a few key methods later in the article.

Our Naked Object Exposed

Naked Object - Person
Diagram 1. Our naked person

We did not code anything GUI-specific. We simply created a business object, and the Naked Objects framework, using reflection, created the Person view. Remember that if our object supports it, the user gets it.

Diagram 2 shows a quick view of some of the built-in Naked Object types. For details on these types beyond what is presented in the examples in this article, please consult the Naked Objects documentation.

Example Types
Diagram 2. Example types

Unit Testing Naked Objects

Now that we have seen how to create a simple naked object, let's turn our attention to unit testing. The Naked Objects framework provides a very slick way to unit test our objects. Here is the start of our test fixture:

package com.briancoyner.naked.addressbook;

import org.nakedobjects.object.NakedClass;
import org.nakedobjects.testing.View;
import org.nakedobjects.testing.NakedTestCase;

import java.util.Calendar;

public class TestPerson extends NakedTestCase {

   /**
   * Yes, you must supply a constructor. Hopefully the next version of the
   * Naked Objects framework will use JUnit 3.8.1.
   */
   public TestPerson(String name) {
      super(name);
   }

   protected void setUp() throws Exception {

      // initialize an object store, otherwise a null pointer exception
      // is thrown when trying to create a new View instance.
      init();
      registerClass(Person.class);
   }
}

This code should look very familiar if you have worked with JUnit. The only exception is a Naked Object's test fixture extends NakedTestCase. This is a base class that extends from junit.framework.TestCase, and provides several convenience methods to register objects and set up an object store. We will talk more about object stores later. Let's test getting and setting the Person's attributes.

Testing the Object

public void testPersonAttributes() {
   Person person = new Person();
   person.getFirstName().setValue("Brian");
   person.getLastName().setValue("Coyner");

   // Note that the Naked Object Date starts with 1 (1 = Jan, 12 = Dec).
   // This is different than java.util.Calendar.
   person.getBirthdate().setValue(1900, 9, 22);

   assertEquals("First Name.", "Brian", person.getFirstName().stringValue());
   assertEquals("Last Name.", "Coyner", person.getLastName().stringValue());
   Calendar calendar = Calendar.getInstance();
   calendar.set(1900, Calendar.SEPTEMBER, 22, 0, 0, 0);
   calendar.set(Calendar.MILLISECOND, 0);

   assertEquals("Birthdate.", calendar.getTime(), person.getBirthdate().dateValue());
}

Once again, if you are familiar with how to write tests, then this test should be fairly straightforward. There are a few things to mention, though, before we continue. First, remember that our person does not contain any "setters". The Naked Objects framework only requires "setters" if an instance of an object can be changed. TextString objects are mutable, so we simply retrieve the TextString and change the value. This is somewhat different than a lot of APIs we are used to. Here, we simply retrieve a reference to a TextString object that holds the first name, and change the value.

person.getFirstName().setValue("Brian");

Let's remove the deep chaining to see what is going on.

TextString firstName = person.getFirstName();
firstName.setValue("Brian");

Second, the Naked Object's Date uses slightly different values for representing months. The Naked Object's Date starts months at 1 (1=Jan., 12=Dec.). The java.util.Calendar starts months at 0 (0=Jan., 11=Dec.).

Testing the View

One of the most impressive and powerful features of the Naked Objects framework is the use of Views. Views represent the "graphical" equivalent of an object, but without the object visible on the screen. This allows us to test our objects and their interactions without special scripting or complex GUI-testing frameworks. Here is how we can test our Person using Naked Object views.

public void testPersonView() {
   String viewName = NakedClass.getNakedClass(Person.class).getPluralName();
   View person = getClassView(viewName).newInstance();

   person.fieldEntry("First Name", "Brian");
   person.fieldEntry("Last Name", "Coyner");
   person.fieldEntry("Birthdate", "1/12/1999");

   person.assertFieldContains("First Name", "Brian");
   person.assertFieldContains("Last Name", "Coyner");

   // The Naked Object's Date object converts the date to this format
   person.assertFieldContains("Birthdate", "Jan 12, 1999");
}

Testing views requires understanding how the framework locates objects (views) and their fields. The first few lines of this test retrieve the person view. All views are retrieved using the plural name. If you were to print out the plural name for our Person you would see "Persons". Obviously, we would like to have the plural name be "People". We will see how to do this at the end of the article.

To specify field names, you must break apart the "getter" methods. For example, getFirstName() becomes "First Name". Simply remove "get" and put a space between characters that differ in capitalization. A View also provides various assert methods. Our test asserts that the given fields contain the correct values.

One Last Feature ...

Remember that every naked object must implement a method that returns back a title. This method comes from the AbstractNakedObject base class and looks like this:

public abstract Title title();

And here is how we might implement it:

public Title title() {

   String title = "";

   // stringValue() returns null if the value is not specified.
   if (!firstName.isEmpty()) {
      title += firstName.stringValue();
   }

   if (!lastName.isEmpty()) {
      title += (title.length() > 0) ? " " + lastName.stringValue() :
       lastName.stringValue();
   }

   return new Title(title);

}

Titles show up at the top of each object's window (next to the icon). You can set a title to anything you like. Just make sure it is descriptive enough for a user to understand the object.

Person with a title
Diagram 3. Person with a title

Pages: 1, 2

Next Page » 

View all java.net Articles.

 Feed java.net RSS Feeds