When creating a Java application, a developer must often strike a balance between conflicting requirements: You want your classes to follow standard object-oriented programming (OOP) guidelines; however, the resulting application must be fast and easy to maintain and must be completed on time. If you rigorously follow all OOP guidelines and make your classes bulletproof under any condition, you will have trouble meeting project deadlines (not to mention the performance problems). This article explores a few simple suggestions I have found helpful in striking a balance between OOP purity and application simplicity.
Public Fields Are Your Friends
Most of the time, you want your class fields (variables) to be private or protected. The idea is to provide the mutator methods SET and GET for each field that should be accessible by other classes. You should make sure that the mutators create copies of the fields in question if they are not primitive data types. For example, a method to change the value of a private integer field called creditLimit would look like the setCreditLimit method shown in Figure
1. Notice that creditLimit object variable is assigned the value of the integer parameter newLimit. This technique prevents access to the private fields of your class without going through the SET/GET methods.
By encapsulating access to the attributes of a class with GET and SET methods youll gain other benefits such as the ability to validate the data with sanity checks for such things as making sure the order date isnt later than the invoice date. The SET/GET methods are also a requirement if you want your class to be a JavaBean (see the Bean discussion later in this article).
So far, were in safe OOP territory. Coding all of these SET/GET methods is a lot of work, though, and can add many lines of code to your class. So is this all really necessary? Is there a valid reason for breaking OOP encapsulation rules, making a field public, and allowing other classes to manipulate the field directly? The answer is yes (to improve performance and to simplify the use of immutable objects). Consider the case for using transfer objects. The primary purpose of a transfer object is to convey information from one object to another. An event is a good example of a transfer object. I use custom events to let an object know that something has happened and to provide supporting
information. Figure 2 is an example of a simple class that encapsulates information about a custom event.
I use the TimeSyncEvent class in my TimeSync utility (see my article Keeping Time with the AS/400 on page 43 for more information about TimeSync) to let registered listeners (i.e., associated Java routines) know that an event has occurred, specifically that an AS/400s system values for QDATE and QTIME have just been modified. The associated routines asynchronously receive the TimeSyncEvent object, at which time they react to the information contained in the object.
The syncTime field inside the event object tells you the new system time on the AS/400. Notice in Figure 3 that the syncTime field is public. A registered listener might process the event in its actionPerformed method. Does the code in Figure 3 violate OOP principles? Sure, it has violated the most basic of encapsulation rules: Never allow external access to the fields of a class. The TimeSyncEvent class, because its timeSync field is public, allows other classes to directly access the syncTime field directly without the use of a GET method. Is this OK? In the case of transfer objects, I think this kind of simplification is perfectly acceptable, because they arent going to break anything by changing the value of the timeSync field; the TimeSyncEvent class is purely informational.
Avoid Method Nesting
Method nesting is a term I give to classes that take a linear process and break it up into a series of methods that must be called in sequence. For example, method A calls method B, which in turn calls method C. This kind of coding often results from the viewpoint that methods ought to be small. In fact, I have spoken to programmers who use the amount of source they can see on one or two screens as the rule of thumb when deciding to break up a large method into a series of smaller methods. Recently, a colleague took one of my methods that had nested for loops and broke it into three or four smaller methods. Each of the new methods basically represented one level of the four loops. Did the final result do anything to make the code more readable or maintainable? Since I use IBMs VisualAge for Java (VAJ), the answer for me is no. Now I have to jump from one method to another (not easy, since VAJ orders methods alphabetically) to follow the logic of what was once a simple process.
So when should you consider breaking a method up into several smaller methods? The guidelines in Java are the same as those you would use in a well-written RPG subroutine. A Java method (just like a good RPG subroutine) should do two things. The first thing the method should do is narrowly focus on a single task. A getOrderHeader method, for example, should not also try to build some type of display and present that information to the user. You would expect getOrderHeader to perhaps access the database, retrieve a specific order header record, and return the result as a vector or maybe a java.sql.ResultSet.
The second thing a Java method should do is consolidate repeated code. If you have several methods with nearly identical sections of code, consider putting the repeating sections in a single, separate method. This will make your code more readable and likely reduce errors. (Programmers Axiom No. 14: If you are doing the same thing in two separate places, it is wrong in one.) For example, if you have two separate places where you validate an account number, move the code to a separate method called boolean isAccountValid(int accountNumber). Now if someone later wants to change the rules that make an account number valid, they can go to one place to modify the code.
Readability is important. A method that takes 15 pages to print is hard to comprehend and, hence, hard to maintain. Of course, such a long method probably isnt following the guidelines listed above, either.
Resist Excessive Subclassing
There is a tendency in Java (especially when making enhancements to someone elses code) to overuse the features of OOP inheritance by arbitrarily designing subclasses. The idea is that you have less chance of messing up someone else who is using the existing class if you just make a new subclass. Whenever youre tempted to create a new subclass, ask yourself whether the new functionality or fields really constitute a unique case or whether they can be folded into the existing class structure as a new method call or property. If your class hierarchy never branches, watch out: You may be guilty of unnecessary subclassing. You probably could collapse several subclasses and superclasses without a loss of clarity or functionality.
You already may have seen the equivalent of excessive subclassing in the world of RPG. Perhaps you brought in a consultant to add a new menu option to your green-screen application. If the consultant made a copy of an existing program, changed a couple of lines, and added the menu option, you are a victim of the same bad practice. To lend a little more credence to my suggestion to reduce excessive subclassing, consider the following quote from Java Report (July 2000): Then there are the people who go, Ooooh, objects! Theyre cool! Lets make gazillions of them! Actually, that feels like almost a fault of the educational system, because if you look what lots of kids fresh out of school have been taught, its, Objects are good, define lots of them! So you get programs with zillions of little adapters, with methods that are one or two lines long, and you start profiling them and theyre spending all of their time doing method dispatching. And who was it that said that? Gary Goslingthe creator of Java.
To Bean or Not to Bean...
JavaBeans are great. Visual toolslike IBMs Visual Composition Editor (VCE), a feature of VAJflourish because of JavaBeans. A set of well-written JavaBeans can help you construct much of an application quickly, much like assembling Legos. In fact, JavaBeans are so useful that some programmers feel that all classes should be JavaBeans. At its core, a JavaBean is simply a Java class that follows strict naming conventions for methods. Private fields must be accessed by the SET/GET methods. For example, if you want to allow someone to access a private field named fileName, you must provide methods like setFileName or getFileName. Tools like IBMs VCE would then automatically recognize that your class has a property called filename. Most such tools also require you to add implements java.io.Serializable to your class definition so the tool can store your class on disk.
Sounds easy, and it is to a certain point. When a class is packaged as a JavaBean, however, there is an expectation of a certain amount of robustness. You, the programmer, must assume that other programmers will use your new Bean in ways that you never anticipated. You may need to provide many new events, just in case someone wants them someday. Since you cant be sure of the context of your JavaBeans use, you must add code to make sure that all method parameters are carefully validated. Robustness is a nice characteristic for a class, but that robustness comes at a cost: You will have to decide where to draw the line between having the robustness and reusability of a class and getting your project finished on schedule.
Following the general naming conventions for Bean methods is a good idea, but dont expect your classes to behave appropriately in all situations with any kind of input. In other words, it is OK to write a class with a given environment in mind (a specific problem and data domain, in OOP terms). Doing so does not make you a bad programmer; just keep the intended user of your classes in mind.
Practically Done
Set some Java coding standards. As a start, you can download one of the many Java style guides from the Internet (start with java.sun.com). Be consistent: If you are going to bend some of the OOP rules, at least do it the same way every time. Always consider the
intended environment and who will likely be maintaining your classes. It is hard to go wrong if you always try to make your classes easy for someone else to later enhance.
Learn OOP principles. Take a class (no pun intended) or read a book that will introduce you to OOP, such as Object-Oriented Technology: A Managers Guide (Second Edition). You must know the rules before you can make an informed decision to deviate from those rules. Remember, learning Java syntax is not the same thing as knowing OOP design principles.
REFERENCES AND RELATED MATERIALS
Object-Oriented Technology: A Managers Guide (Second Edition). David A. Taylor, Ph.D. Reading, Massachusetts: Addison-Wesley Publication Co., 1998
public void setCreditLimit(int newLimit)
throws CreditLimitException
{
if (newLimit > CORP_CREDIT_LIMIT)
{
throw new CreditLimitException();
}
creditLimit = newLimit;
}
Figure 1: Basic OOP encapsulation techniques make class fields private and provide GET and SET methods for retrieving and modifying the values of a field.
public class TimeSyncEvent extends ActionEvent
{
//kind of events supported
public static final int SYNC = AWTEvent.RESERVED_ID_MAX + 5556;
public GregorianCalendar syncTime = null;
public TimeSyncEvent(Object source, int id, String command)
{
super(source, id, command);
}
}
public void actionPerformed(ActionEvent evt)
{
if(evt instanceof TimeSyncEvent)
{
try
{
String dateTimestr = dfmt.format(
((TimeSyncEvent)evt).syncTime.getTime());
//update a screen or do something useful here
Figure 2: The TimeSyncEvent class breaks encapsulation rules by making the syncTime field public.
}
catch(Exception e) {}
}
}
Figure 3: The actionPerformed routine has direct access to the syncTime field but cant break anything.
LATEST COMMENTS
MC Press Online