 | Level: Introductory Andrew Glover (aglover@stelligent.com), President, Stelligent Incorporated
22 May 2007 You know tight coupling is bad news and you really want
to avoid it in your designs -- but the question is how. This month, learn
how to recognize a tightly coupled system and then disentangle it using the
Dependency Inversion Principle.
In the past year or so of writing this column, I have introduced many
tools and techniques that you can use to improve the quality of your code.
I've showed you how to apply code metrics to monitor the attributes of your
code base; how to use test frameworks like TestNG, FIT, and Selenium to
verify application functionality; and how to use extension frameworks like
XMLUnit and StrutsTestCase (and powerful helpers like Cargo and DbUnit) to
extend the reach of your testing framework.
While code metrics and developer testing are fundamental to ensuring code
quality throughout the development process (like I always say, test early
and often!), they are essentially reactive techniques. You test and measure
your code to ascertain and quantify its quality, but the code
itself has already been written. No matter what you do, you are stuck with
the original design.
Of course, there are better and worse ways to design a software system.
One of the keys to good design is keeping an eye on maintainability. Poorly
designed and implemented systems may be easy to write but they're a real
challenge to support. They tend to be dangerously brittle -- meaning that
changes to one area of the system will affect other seemingly unrelated
areas -- and therefore difficult and time consuming to refactor. Adding
developer tests to the code base gives you a map to work from, but the
progress itself is still painstakingly slow.
You can improve already written code by refactoring it, but introducing
change after the fact is generally expensive. Wouldn't it be easier if the
code was just written well to begin with? This month, I'm
introducing a proactive technique for ensuring the quality and maintainability
of your software systems. The Dependency Inversion Principle has
proved essential for writing high-quality, maintainable, and therefore
testable code. The basic idea behind dependency inversion is that objects
should be dependent upon abstractions and not upon
implementations.
 |
It's dependency inversion, not dependency injection
The Dependency Inversion Principle is not directly related to dependency
injection. Dependency injection, sometimes known as inversion of control (IOC), refers to using a framework like Spring to link object dependencies at
run time rather than at compile time. While dependency inversion and
dependency injection need not be used together, they are
complementary: both techniques strive to utilize abstractions over
implementations. See Resources to learn more about
the Dependency Inversion Principle. |
|
The too-tight couple
You probably have at least heard the term coupling used in the
context of object-oriented programming. Coupling refers to the
interrelationship(s) of components (or objects) in an application. A loosely
coupled application is more modularized than a tightly coupled one. Its
components rely on interfaces and abstract classes as opposed to concrete
ones, as they would in a tightly coupled system. In a loosely coupled
system, components are interrelated using abstractions instead of
implementations.
It's easy to grasp the problem of tight coupling when you see it in a diagram. For instance, take a look at Figure 1, which represents a
software system whose GUI is coupled to its database:
Figure 1. A tightly coupled system
The GUI's dependence on an implementation rather than an abstraction
limits the system. You cannot operate the GUI without having the database up
and running. This design doesn't seem so bad from a functional standpoint -- we've been writing apps like this for years and planes aren't falling out of the sky, after all -- but testing it is another story.
Did you say 'brittle'?
The system in Figure 1 makes it hard to isolate programming concerns, which is essential to testing and maintaining every aspect of a system. You'll need a live database properly seeded with lookup data to test the GUI and a properly working GUI to test the data access logic. You could test the front end with TestNG-Abbot (now renamed to FEST), but that still won't tell you anything about the database functionality.
You can see this nefarious coupling in action below in Listing 1. A
particular button for the GUI defines an ActionListener, which communicates directly with the
underlying database via the getOrderStatus
call.
Listing 1. An ActionListener is defined for a button in the GUI
findWidgetButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
try {
String value = widgetValue.getText();
if (value == null || value.equals("")) {
dLabel.setText("Please enter a valid widgetID");
} else {
dLabel.setText(getOrderStatus(value));
}
} catch (Exception ex) {
dLabel.setText("Widget doesn't exist in system");
}
}
//more code
});
|
When the GUI's button component is clicked, a particular order's status is retrieved
directly from the database, as shown in Listing 2:
Listing 2. The GUI communicates directly with the database via the getOrderStatus method
private String getOrderStatus(String value) {
String retValue = "Widget doesn't exist in system";
Connection con = null;
Statement stmt = null;
ResultSet rs = null;
try {
con = DriverManager.getConnection("jdbc:hsqldb:hsql://127.0.0.1", "sa", "");
stmt = con.createStatement();
rs = stmt.executeQuery("select order.status "
+ "from order, widget where widget.name = "
+ "'" + value + "' "
+ "and widget.id = order.widget_id;");
StringBuffer buff = new StringBuffer();
int x = 0;
while (rs.next()) {
buff.append(++x + ": ");
buff.append(rs.getString(1));
buff.append("\n");
}
if(buff.length() > 0){
retValue = buff.toString();
}else{
retValue = "Widget doesn't exist in system";
}
} catch (SQLException e1) {
e1.printStackTrace();
} finally {
try {rs.close();} catch (Exception e3) {}
try {stmt.close();} catch (Exception e4) {}
try {con.close();} catch (Exception e5) {}
}
return retValue;
}
|
The code in Listing 2 is bogged down especially by the fact that it
communicates directly with a hardcoded database via a hardcoded SQL
statement. Yeeesh! Can you imagine the challenge of developer testing
this GUI and the associated database (which, by the way, could have just as
easily been a Web page)? It gets worse when you think about actually
modifying the system, given that any change to the database will
affect the GUI.
 |
The DAO pattern
Data Access Object (DAO) is a design pattern that aims to separate low-level data access operations from high-level business logic using interfaces and associated implementations. Essentially, a concrete DAO class implements the logic for accessing data from a specific data source. The DAO pattern makes it possible to define multiple concrete implementations for multiple databases, or even varying data sources like file systems, using just one interface.
|
|
Turn me loose!
Now let's consider the same system designed with the Dependency
Inversion Principle in mind. As you can see in Figure 2, it is possible to decouple the
application by adding two components to it: one is an interface and the other is an implementation:
Figure 2. A loosely coupled system
In the application shown in Figure 2, the GUI relies on an abstraction -- a data access
object or DAO. The DAO's implementation is directly dependent on the database, but the
GUI itself is not entangled. Adding an abstraction in the form of a DAO decouples the database implementation from the GUI implementation.
In place of the database, an interface is now coupled to the GUI code. The interface is
shown in Listing 3:
Listing 3. WidgetDAO is an abstraction that helps decouple the architecture
public interface WidgetDAO {
public String getOrderStatus(String widget);
//....
}
|
The GUI's ActionListener code references the
interface type WidgetDAO, defined in Listing 3, and not
the actual implementation of that interface. In Listing 4, the GUI's getOrderStatus() method essentially delegates to the
WidgetDAO interface:
Listing 4. The GUI relies on the abstraction, not the database
private String getOrderStatus(String value) {
return dao.getOrderStatus(value);
}
|
The actual implementation
of the interface is totally hidden from the GUI because it requests the implementation
type from a factory, as shown in Listing 5:
Listing 5. The WidgetDAO implementation is hidden from the GUI
private WidgetDAO dao;
//...
private void initializeDAO() {
this.dao = WidgetDAOFactory.manufacture();
}
|
Evolution is easy
Note how the code taken from the GUI in Listing 5 references the
interface type only -- the implementation of the interface is not used (or imported) anywhere in the GUI. This abstraction of implementation details is key to flexibility: it enables you to swap out the implementation type completely without affecting the GUI.
Also note how the WidgetDAOFactory from
Listing 5 shields the GUI from the details of how the WidgetDAO type is created. That's the responsibility of
the factory, which is shown in Listing 6:
Listing 6. A factory hides implementation details from the GUI
public class WidgetDAOFactory {
public static WidgetDAO manufacture(){
//..
}
}
|
Having the GUI refer data retrieval to an interface type gives you
the flexibility to create different implementations. In this case, a
database holds widget information, so you can create a WidgetDAOImpl class that communicates directly with the
database, shown in Listing 7:
Listing 7. The WidgetDAO type does the dirty work
public class WidgetDAOImpl implements WidgetDAO {
public String getOrderStatus(String value) {
//...
}
}
|
Note that implementation code isn't included in these examples.
That code isn't important -- it's the principle that counts. You shouldn't
really care how the WidgetDAOImpl's getOrderStatus() method works. It could grab the status
from the database or from a file system -- the whole point is, it doesn't
matter to you!
Now, isolate the GUI
Because the GUI now relies on an abstraction and obtains an implementation of that
abstraction from a factory, you can easily create a mock class that isn't coupled to a database or file system. The mock isolates the GUI as shown in Listing 8:
Listing 8. Isolation made easy
public class MockWidgetDAOImpl implements WidgetDAO {
public String getOrderStatus(String value) {
//..
}
}
|
Adding a mock is the final step in designing this system for
maintainability. Isolating the GUI from the database and vise versa means
you can test the GUI without concern for specific data. You can also test
the data access logic without concern for the GUI.
In conclusion
You may not think about it much, but the applications you are designing
and building today could easily outlast you. You'll move on to other
projects and companies, but your code, like COBOL, will stay behind,
possibly even for decades to come.
One thing developers have come to agree on is that well-written code is
maintainable, and the Dependency Inversion Principle is a sure way to design
for maintainability. Dependency inversion stresses reliance on abstractions
over implementations, which creates a great deal of flexibility within a
code base. Applying this technique with the help of a DAO, as you've seen
this month, ensures that not only will you be able to modify your code base
when you need to, but so will other developers.
Resources - Participate in the discussion forum.
- "The Dependency Inversion Principle" (Robert C. Martin): Introduces the Dependency Inversion Principle, including plenty of examples.
- "Advanced DAO programming" (Sean Sullivan, developerWorks, October 2003): Java developer Sean Sullivan discusses three often overlooked aspects of DAO programming: transaction demarcation, exception handling, and logging.
- "The COBOL jigsaw puzzle: Fitting object-oriented and legacy applications together" (E.S. Flint, IBM Systems Journal, Volume 36, Number 1, 1997): An old gem that
explores some of the conundrums of migrating legacy code into a newer system.
- "
In pursuit of code quality: Testing Struts legacy apps" (Andrew Glover, developerWorks, July 2006): Speaking of legacy apps, have you
tried modifying a Struts application lately?
- "
In pursuit of code quality: Automate GUI testing with TestNG-Abbot" (Andrew Glover, developerWorks, February 2007): Get started with the testing framework recently renamed FEST.
- "Dependency injection in Apache Geronimo, Part 1: A new way to look at decoupling in J2EE applications" (Neal Ford, developerWorks, February 2006): Familiarize yourself with dependency injection, using Apache Geronimo as a
working example.
- "Use Inversion of Control in method signatures" (Andre Fachat, developerWorks, January 2007): Learn how to use IOC to decrease the coupling between components and improve performance.
-
In pursuit of code quality series (Andrew Glover,
developerWorks): Learn more about writing quality-focused code.
-
developerWorks: Hundreds of articles about every aspect of Java programming.
About the author  | 
|  | Andrew Glover is president of Stelligent Incorporated, which helps companies address software quality with effective developer testing strategies and continuous integration techniques that enable teams to monitor code quality early and often. Check out Andy's blog for a list of his publications. |
Rate this page
|  |