Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

More on getters and setters

Build user interfaces without getters and setters

  • Print
  • Feedback

Page 6 of 6

Let's assume you built an input form using the HTMLExporter I just discussed (that creates three <input...> fields named name, id, and salary). The following code shows how a servlet can build an Employee using the data from that form. It uses my "reverse builder" to initialize an Employee from the ServletRequest object passed to an HTTPServlet's doPost() or doGet() method:

class myServlet extends HTTPServlet
{   
    void doPost(HttpServletRequest request, HttpServletResponse response) 
    {   Employee fred = new Employee( new HTMLImporter(request) );
        //...
    }
    //...
}


Listing 5 shows the sources for the HTMLImporter. As you can see, it just extracts the required data from the ServletRequest and relays it to the Employee under construction.

Listing 5. Builder-based initialization in a servlet

   1  import javax.servlet.ServletRequest;
   2
   3  class HTMLImporter implements Employee.Importer
   4  {   ServletRequest request;
   5
   6      public void open() { /*nothing to do*/ }
   7      public void close(){ /*nothing to do*/ }
   8
   9      public HTMLImporter( ServletRequest request )
  10      {   this.request = request; 
  11      }
  12      public String provideName()
  13      {   return request.getParameter("name");
  14      }
  15      public String provideID()
  16      {   return request.getParameter("id");
  17      }
  18      public String provideSalary()
  19      {   return request.getParameter("salary");
  20      }
  21  }
  22
  23  /***********************************************************************
  24   * Stub for debugging. The ServletRequstAdapter (not shown) just
  25   * implements every method of the {@link javax.servlet.ServletRequest}
  26   * interface to throw an {@link UnsupportedOperationException}.
  27   */
  28
  29  class MyServletRequest extends com.holub.servlet.ServletRequestAdapter
  30  {
  31      String name, id, salary;
  32
  33      public MyServletRequest( String name, String id, String salary )
  34      {   this.name   = name;
  35          this.id     = id;
  36          this.salary = salary;
  37      }
  38
  39      public String getParameter( String requested )
  40      {   if( requested.equals("name")        ) return name;
  41          else if( requested.equals("id")     ) return id;
  42          else if( requested.equals("salary") ) return salary;
  43          else throw new Error("Unexpected Paramether");
  44      }
  45  }


It's marginally harder to build an Employee with an interactive client-side UI, but that's possible too. Figure 3 depicts the JComponentImporter class to initialize an Employee using the UI. Use it like this:

JFrame parent = ...;
Employee wilma = new Employee( new JComponentImporter(parent) );


Listing 6 displays the source code for JComponentImporter. The open() method (line 16) does the hard work of building the dialog box and displaying it. In this particular implementation, open() doesn't return until the user dismisses the dialog by hitting the Okay button. The remaining methods just get the information from the dialog.

Listing 6. Builder-based client-side initialization

   1  import javax.swing.*;
   2  import java.awt.*;
   3  import java.awt.event.*;
   4
   5  class JComponentImporter implements Employee.Importer
   6  {
   7      private JTextField  name   = new JTextField(20);
   8      private JTextField  id     = new JTextField(20);
   9      private JTextField  salary = new JTextField(20);
  10      private JDialog frame;
  11
  12      public JComponentImporter( Frame parent )
  13      {  frame = new JDialog( parent, "Test Input", true );
  14      }
  15
  16      public void open()
  17      {
  18          Container content = frame.getContentPane();
  19          content.setLayout( new GridLayout(4,2) );
  20          content.add( new JLabel("Name:  ") );
  21          content.add(  name );
  22          content.add( new JLabel("Employee ID:  ") );
  23          content.add(  id );
  24          content.add( new JLabel("Salary:  ") );
  25          content.add(  salary );
  26
  27          JButton okay = new JButton("Okay");
  28          okay.addActionListener
  29          (   new ActionListener()
  30              {   public void actionPerformed(ActionEvent e)
  31                  {   frame.dispose();
  32                  }
  33              }
  34          );
  35          content.add(new JLabel(""));    // place holder
  36          content.add(okay);
  37
  38          frame.pack();
  39          frame.show();
  40      }
  41
  42      public String provideName()     {   return name.getText();   }
  43      public String provideID()       {   return id.getText();     }
  44      public String provideSalary()   {   return salary.getText(); }
  45
  46      public void close()
  47      {   name = id = salary = null;
  48      }
  49  }


Figure 3. Client-side initialization

Consequences of refactoring the Context

Obviously, the Builder pattern doesn't eliminate the accessor and mutator methods, but—and this is critical—the accessors and mutators are now associated with the Builder, not with the business object. This means that changes to implementation of the business object will not ripple out into the program as a whole—our main goal in encapsulating implementation.

The choice of methods in the Builder interface does expose some Context-related implementation information to the Builder derivatives, and makes them susceptible to change when the business object changes. In the current example, I tried to minimize that impact by using String arguments to pass information to (and get information from) the builders, but that's not always possible. Nonetheless, the pattern does not—and again, this fact is critical—expose business-object implementation details to anyone except the Builders. That is, changes to the Employee that mandate changes to the Importer and Exporter interfaces affect only a limited number of classes (the Concrete Builders). If you put get/set methods on the Employee class itself, the scope of the damage in the event of a change is unlimited.

Even though the composition of the Builder interface is a minor weak point, if you design this interface carefully, you can reduce the odds of changes to it. For example, the Importer and Exporter interfaces would be more delicate if I imported a salary as a double instead of using a String.

Summing up

A good OO system hides implementation as thoroughly as possible. Dubious programming idioms like get/set methods seriously damage the maintainability of the system. Fortunately, design patterns like Builder can provide ways to eliminate get/set methods—or at least limit their scope—while improving the program's structure and maintainability. It's a win-win situation. Builder is not the only solution to the get/set-elimination problem, by the way, but it's effective in many situations.

If you decide not to use the get/set idiom, thereby forcing yourself to think of alternative ways to accomplish the same end, your code will become easier to maintain and more object oriented. The main rule is "don't ask for the data that you need to do something; ask the object that has the data to do the work for you." (OO designers call this principle "delegation.") Similarly, if you keep your design in the problem domain as long as possible, as I described in the previous getter/setter article, then get/set methods tend not to appear in the first place.

I know this article will probably engender as much trash in the associated Talkback discussion as the last one; so let me finish up with a few additional points. As I said in the first article (though this point seems to have been ignored by the most vitriolic of the posters), you can't eliminate get/set methods entirely from any program. For example, you'll often need them at the "procedural boundary" of the program, where you create general-purpose classes that must exist outside of the domain of a particular problem, and these classes must communicate with a procedural subsystem like the operating system or a relational database server. Low-level UI classes like the Swing components also typically need at least a few get/set methods, primarily because they're so generic in structure.

Giving up procedural idioms is neither easy nor comfortable. Too many things that are "right" in a procedural context are "wrong" in an OO context. I've heard many lame excuses for staying in the procedural comfort zone. One of the contributors to the previous article's discussion, for example, claimed that hard-core data abstraction such as I've described was worthless in situations where maintenance was not important, which he infers, is often the case. I can't personally imagine any situation where maintenance is not important, however. First of all, throw-away programs never seem to be thrown away. I've worked on 20-year-old "throw-away" code. More importantly, my code hits maintenance roughly two seconds after I finish writing it. Whenever I look at code I wrote yesterday, I'm in maintenance mode. I imagine that the writer of that comment is just a much better programmer than me. He writes perfect code right off the bat and never looks back at his code once it's written. Though I can aspire to that level of perfection, I'm not there yet. I'm constantly refactoring my code to improve it. Every time a business rule changes—and this often happens while the program is under construction—the existing code is effectively in maintenance.

Giving up "procedural thinking" isn't easy, but it's worth doing.

About the author

Allen Holub has worked in the computer industry since 1979. He currently works as a consultant, helping companies not squander money on software by providing advice to executives, training, and design-and-programming services. He's authored eight books, including Taming Java Threads (Apress, 2000) and Compiler Design in C (Pearson Higher Education, 1990), and teaches regularly for the University of California Berkeley Extension. Find more information on his Website (http://www.holub.com).

Read more about Core Java in JavaWorld's Core Java section.

  • Print
  • Feedback

Resources