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
|