When building an application, you need to follow a principle of object orientation—encapsulation, also known as data hiding. The more data you operate with, the more objects you need. Creating and maintaining such objects can be a tedious and error-prone task. You can avoid such problems and build more robust applications faster by applying the object data holder framework. The cornerstone of the framework is the data holder interface, which allows uniform data storing.
Keep data class properties in collections of name-value pairs
Almost every object-oriented application has a number of classes that only have fields with methods for setting and getting them. These classes are data classes (or dump data holders) that represent real-world objects. Such classes are the essential bricks of the application and are simple by nature. They don’t have any behavior, at least at the beginning of their development phase. You can easily create a data class by specifying fields and providing methods for accessing them. Quite often these fields are a diverse set of properties (like int, double, String, etc.) that have to be explicitly coded and initialized for each data class. And this is the main drawback! You can overcome it by keeping all class properties in a collection containing name-value pairs. Let's explore it in detail.
The essence of the framework is the Holder interface that provides basic data storing and accessing possibilities (Listing A). The interface is similar to java.util.Map interface, but it operates with data through the public Entry interface (Listing B). The Entry interface defines access to a named property and permits hiding and constraining. The hiding can be used for controlling an entry visibility, and constraining is useful for blocking the entry from being modified. This interface can be thought of as the java.beans.BeanInfo interface, but it’s lightweight and has a minimum of GUI-related attributes, namely isHidden().
The class diagram in Figure A illustrates the object holder framework.
|Object holder framework class diagram|
The default implementation
The framework has the default implementation of the interfaces. However, you can easily extend it or create your own implementation. The DataEntry class (Listing C) implements the Entry interface. Note that the setValue() method throws a checked EntryConstrainedException exception if the entry can’t be modified. Thus, this method call has to be wrapped in a try-catch block. In addition to the interface signature, it has the equals(), hashCode(), and toString() methods. The first two methods are supported for the benefit of hashtables, although the last one is mainly for debugging purposes. The next default implementation is the DataHolder class (Listing D). It holds data entries and provides named access to them. This class is backed up by a synchronized HashMap, so it should be in a multithreaded environment. Another thing to note is that the entrySet() method returns an unmodified view of the entries. It encapsulates the collection restricting access to it only through addEntry(), removeEntry(), and getEntry() methods. Some of the DataHolder class methods like getEntry() can throw an unchecked exception, NoSuchElementException, if there is no such named entry in this holder. The main purpose of this exception is to guarantee that a data holder is properly initialized before usage and eliminate entry name misspellings. The getEntry() method can be called out of the try-catch block, but precautions must be taken in case of run time exceptions. Along with the Holder interface methods, the DataHolder class has a set of convenient methods to directly access a named entry value: getObject(), getInt(), getString(), etc. All these methods are protected and should be used only by subclasses.
The DataEntry and DataHolder classes implement the java.io.Serializable interface that allows serializing and passing them on as a stream of bytes (e.g. using a socket connection).
Now you’re ready to create your own implementation of DataHolder.
Creating your own data holder
Let's assume you want to create a data class representing the DEPT table of Oracle's default user, Scott. This table has three fields: DEPTNO as NUMBER, DNAME as VARCHAR2, and LOC as VARCHAR2. Knowing field names and types, you can easily create a desired data class Department (Listing E). This class also works as an adapter to the DataHolder class, so it can implement any interface that a client wishes to use. In our case it’s: getName(), getNumber(), getLocation(), and so on (Listing E). By now you’ve created a simple data class with minimum effort and good performance!
Initialization of the object can be done in its constructor or by using a helper object that creates DataEntry objects and adds them to the Department (Listing F). This example uses the database as a source of data entries, but it can be a file, network, and so on. Note that the class property names (DEPTNO, DNAME, LOC) are the same as that of the table fields, so you can use them directly in building SQL queries in run time.
If data is stored in an XML file, it’s advantageous to use XMLDecoder and XMLEncoder classes from the java.beans package (JDK 1.4) for reading and writing DataHolder objects. The only thing you have to do is to create a custom PersistenceDelegate for the DataHolder.