HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML

In Part 1 (JDJ, Vol. 7, issue 6) we looked at the Java class as a type. Although it's easy to think of the class name of our Java class as its type, the interfaces it implements and the superclasses it extends can also be viewed as its types.

In Part 2 I'll explore using interfaces and abstract classes to achieve flexibility with a real-world example that implements the Data Access Object (DAO) pattern. I'll also quickly look at the abstract classes and interfaces in the Java arena.

Putting It to Real Use the DAO Pattern
Data can be stored in different persistent data sources. These include relational databases as well as flat files, XML documents, LDAP, and legacy systems. Each data source requires a different way of getting a connection to it as well as various ways of retrieving, adding, updating, and removing data. With the DAO pattern you can separate the data source access and encapsulate interaction with the data from objects that use the data.

In any given application we may have a defined business entity called Customer. Rather than code all the data access logic in the Customer object, we'd want to separate this because the business that needs the application may store data for its customers in different databases. Some of the data may even be in legacy systems that require lots of code written with a proprietary API to get the data out. Keeping the data access logic out of the Customer business object makes the code cleaner and allows the developer to focus on the business needs instead of how to get the data.

Another practical use for the DAO pattern would be a product company that wants their application to work with multiple relational databases, allowing customers to use the DBMS of their choice. In either case we would want to use the DAO pattern to encapsulate data access from the rest of our application.

We can create a flexible implementation of this pattern using an abstract class and an interface. For example, a company has an application that needs to get customer data from either an Oracle or a Microsoft SQL Server DBMS. Because the SQL syntax for the Oracle and the Microsoft DBMS are not compatible, we'll need to write a separate class that can access the customer data from either database. These classes are represented by the OracleCustomerDAO and the MSSQLCustomerDAO. Because the set of methods for each of these objects is the same (get, add, update, delete) but they don't share any common implementation, we'll define an interface CustomerDAO as follows:

public interface CustomerDAO {
public CustomerDatagetCustomer
(String id);
public StringaddCustomer
(CustomerData cd);
public booleanupdateCustomer
(CustomerData cd);
public boolean deleteCustomer(Stringid);
}

The OracleCustomerDAO and the MSSQLCustomerDAO would implement each method using the specific SQL syntax of the DBMS to perform the operations.

The CustomerData object encapsulates all the data about our Customer. This class typically uses Java fields to hold the data and has no methods (other than accessor get/set methods). This is an example of the Value Object pattern.

Next we need to consider how to allow the application to get the proper CustomerDAO implementation without each class that needs customer data trying to determine which database is in use. Since this is a common operation, we can place that logic in a separate class that will then create the proper CustomerDAO object. This is called a factory.

Since the Oracle and Microsoft databases require different classes for each business entity we want to access (e.g., Customer, Order, etc.), we'll create a separate factory for each. The OracleDAOFactory class will have a getCustomerDAO method on it that will return a copy of the OracleCustomerDAO object.

public class OracleDAOFactory extends DAOFactory {
public CustomerDAO getCustomerDAO() {
return new OracleCustomerDAO();
}
...
}

The MSSQLDAOFactory has a similar method. Both implementations of the method return an object reference of the type CustomerDAO. This allows the business object, e.g., CustomerBean, to use the object to get customer data through the interface without caring which database the data is coming from.

The last thing we need to do is hide the Factory implementation from the business object. We can do this using a generic abstract class, DAOFactory. This class can implement the code to determine which data source to use and be the superclass for the OracleDAOFactory and the MSSQLDAOFactory. The code to determine which data source is in use is not shown but we could find out by reading in data from a Java properties file or an XML file, or looking it up in a JNDI Naming Service.

The abstract class, DAOFactory, can also define an abstract method, getDAOFactory, that's responsible for returning the correct factory object. It's the job of the subclass factory object to create the correct CustomerDAO for the given data source, as we saw earlier.

public abstract class DAOFactory {
// abstract getXXXDAO methods
public abstract CustomerDAO getCustomerDAO();

// getDAOFactory
public static DAOFactory getDAOFactory() {

String factoryClass = "":

// determine which factory class to use
...

Class _class = Class. forName (factoryClass);

Object _object = _class. newInstance();
if (_object instanceof DAOFactory) {
factoryInstance = (DAOFactory) _object;
}
else {
// throw Exception
}

// Need to handle the
// ClassNotFoundException
// InstantiationException
// IllegalAccessException

return factoryInstance;
}
}

Our business object CustomerBean can now use either an Oracle or a Microsoft database to get, add, update, or delete a customer. The following code shows how a business object might use these objects to delete a customer:

String customer_id = "100";
DAOFactory df = DAOFactory.getDAOFactory();
CustomerDAO cust = df.getCustomerDAO();
cust.deleteCustomer(customer_id);

In this example we're writing to a type instead of a specific implementation. The code doesn't depend on the database that's in use and the application can quickly be changed to use new databases, such as Sybase. A new CustomerDAO class will need to be implemented using the Sybase SQL syntax to access customer data, and a Sybase version of the DAOFactory will need to be written; however, all the business objects that use the customer data won't require any changes.

Wrapping It Up
We've looked at how to write code to a type rather than to an implementation and saw how this can create a tremendous amount of flexibility in our applications. This is because the type determines what the object can do, and the implementation determines how the object does it.

The last thing to consider is how to determine when to use an abstract class and when to use an interface. Interfaces allow classes that don't share any implementation hierarchy (inheritance) to be grouped together and still share a type. However, when we use an interface, we don't get any implementation reuse as we do in the CustomerDAO interface.

When we use the abstract class as a supertype we get implementation reuse that doesn't need to be duplicated across multiple classes. For example, the DAOFactory can share the implementation code that determines which database to use. Using an abstract class, however, locks our class into an inheritance hierarchy and prevents other classes that already extend a particular class from sharing the type. It also prevents classes that extend the abstract class from being able to extend other classes.

This was not a major factor in the DAO pattern implementation but is usually a concern in other designs. A final consideration is that adding additional methods to the interface breaks all the classes that implement the interface, because each class is required to implement the new method, while adding methods to an abstract class can be done without affecting any subclasses.

References

  • Gosling, J., Joy, B., and Steele, G. (1996). The Java Language Specification. Addison-Wesley.
  • Flanagan, D. (2002). Java in a Nutshell. O'Reilly.
  • Alur, D., Crupi, J., and Malks, D. (2001). Core J2EE Patterns: Best Practices and Design Strategies. Prentice Hall PTR.
  • J2EE BluePrints: http://java.sun.com/blueprints/
  • (Image and code) "Reveal the magic behind subtype polymorphism": www.javaworld.com/javaworld/jw-04-2001/ jw-0413-polymorph.html
  • "A primordial interface?": www.javaworld.com/javaworld/jw-03-2001/ jw-0309-primordial.html
  • "Thanks type and gentle class": www.javaworld.com/javaworld/jw-01-2001/jw-0119-type.html

    Author Bio
    Michael Barlotta is the director of technology at AEGIS Inc. (www.AEGIS.net). He's a Sun Certified Java programmer and is a recognized as an expert on Jaguar CTS (EAServer), mentoring clients and speaking at conferences on the topic. Mike is the author of several books including Taming Jaguar and Jaguar Development with PowerBuilder 7 (both by Manning Publications). [email protected]

    All Rights Reserved
    Copyright ©  2004 SYS-CON Media, Inc.
      E-mail: [email protected]

    Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.