J2EE projects tend to be complex. This makes good coding practices vital. In this chapter, we've looked at how good OO practice underpins good J2EE apps. We've also looked at the importance of consistently applying sound coding standards, to allow efficient teamwork and help to ensure that apps are easy to maintain. Finally, we've discussed how to avoid writing code, through use of existing frameworks and – in the last resort – the implementation of our own frameworks. The following table summarizes the OO design principles we've discussed:




Related design patterns

Impact on performance

Code to interfaces, not concrete classes. The relationship between app components should be in terms of interfaces, not classes.

Promotes design flexibility. Works well when interfaces are implemented by JavaBeans, configured through their bean properties.
Doesn't preclude use of concrete inheritance. Implementations can have a parallel but distinct inheritance hierarchy from interfaces.

Marginally more complex to implement than use of concrete inheritance.

Many design patterns are based on interface inheritance.


Prefer object composition to concrete inheritance.

Promotes design flexibility.
Avoids problems with Java's lack of multiple concrete inheritance.
Enables class behavior to be changed at runtime.

May lead to an increased number of classes.
May be overkill for simple requirements.

Strategy (GoF)


Use the Template Method design pattern when you know how to implement a workflow but not how all individual steps should be implemented.

Ensures that the workflow can be implemented and tested once.
Ideal for resolving portability issues in J2EE.

Sometimes delegation is a better model, and the Strategy pattern is preferable.

Template Method (GoF)


Use the Strategy design pattern as an alternative to the Template Method pattern when the flexibility of delegation, rather than concrete inheritance, is desirable.

There's greater freedom when implementing the interface than using concrete inheritance.
The implementation can vary at runtime.
The implementation can be shared with other classes.

Slightly more complex to implement than the Template Method pattern, which is often an alternative.

Strategy (GoF)


Use callback methods to achieve extensibility while centralizing workflow.

Can achieve code reuse when other approaches can't deliver it.
Allows the centralization of error handling code.
Reduces the likelihood of bugs by moving complexity from app code into the framework.

Conceptually complex, although code using it is generally simpler than it would be using other approaches.

A special case of the Strategy design pattern (GoF)

Slight performance degradation if the callback interface is invoked very often

Use the Observer design pattern.

Promotes separation of concerns by decoupling listeners from the execution of business logic that generates events.
Enables extensibility without modification of existing code.

Introduces complexity that isn't always warranted.
Requires an event publication infrastructure, and event classes.
A rogue observer that blocks can lock an app using this pattern.
May not always work in a clustered environment.

Observer (GoF)

Having too many observers (listerns) can slow a system down.

Combine multiple method arguments into a single object.

Allows use of the Command design pattern.
Makes it easier to extend functionality with breaking interfaces.

Increases the number of objects in a system.

Command (GoF) EJB Command (EJB Design Patterns)

Contributes to "object churn." In relatively infrequent calls such as EJB invocation, the cost of the necessary object creation is negligible. In a nested loop, the cost might be severe.

Use unchecked exceptions for unrecoverable errors, and checked exceptions when calling code is likely to be able to handle the problem.

Less code.
More readable code; business logic won't be obscured by catching exceptions that can't be handled.
Enhanced productivity.
No need to catch, wrap and rethrow exceptions; less likelihood of losing stack traces.

Many Java developers are used to using checked exceptions almost exclusively.
When using unchecked exceptions be sure to remember to document those that may be thrown the compiler can't assist.



Use reflection.

A powerful way to parameterize Java code.
Superior to implementing the Factory design pattern.
Very powerful when combined with JavaBeans.
Helps to resolve portability issues in J2EE.

Reflection can be overused. Sometimes a simpler solution is equally effective.

Factory (GoF)

Depends on how often calls are made. Usually there is no significant effect.

Implement app components as JavaBeans.

Makes it easier to configure systems declaratively, consistent with J2EE deployment approach. 174Allows problems such as input validation to be addressed using the standard JavaBeans API.



Usually negligible.

Avoid a proliferation of singletons by using an app context or registry.

Promotes design flexibility.
Enables us to implement the "singletons" as normal JavaBeans; they will be configured via their bean properties.
In web apps, we can put the context in the ServletContext, avoiding the need even for a getInstance() method on the registry. Anywhere within a J2EE server, we can bind the registry in JNDI. We may be able to use JMX.
It's possible to support reloading of "singletons"
The app context can provide other services, such as event publication.
Provides a central point for configuration management inside the app. Configuration management code will be handled by the app context a generic framework object rather than individual app objects.
app developers will never need to write code to read properties files, for examples.
Minimizes dependencies on particular APIs (such as the properties API) in app objects.

Registry will require configuration outside Java, such as an XML document. This is an excellent approach for complex apps, but unnecessary for very simple apps.

Singleton (GoF) Factory
(GoF) Prototype





Impact on performance

Start from JavaSoft's coding conventions.

Makes it easier for new developers to read your code. Familiarity with Sun's conventions makes it easier for you to read the code of others.



Objects and methods should have clear responsibilities.

Makes code self-documenting.
Localizes the impact of changes.



Avoid literal constants in code.

Makes it easier to read and maintain code.
Reduces the likelihood of typos causing subtle problems.



Use only private instance variables.
Provide getter and setter methods as necessary.

Favors black-box class reuse and loose coupling.
Public instance variables allow object state to be corrupted by any other object.
Protected instance variables allow superclass state to be corrupted by subclasses or classes in the same package.

Using private instead of protected instance variables reduces the ability of subclasses to modify superclass behavior. However, this is normally a good thing.

Negligible performance overhead in the use of methods, rather than direct variable access.

Keep a class's public interface to a minimum.

Helps to achieve to loose coupling between classes.
Makes classes easier to use.



Use final methods appropriately.

Final methods can be used to prevent subclasses incorrectly modifying superclass behavior by overriding methods.

Limits the scope of subclasses to customize superclass behavior. However, overriding concrete methods is a poor way to achieve extensibility.

Marginal improvement, as the JVM knows which class the method is defined in.

Implement toString() methods useful during debugging and maintenance.

If all classes have toString() methods debugging is a lot easier, especially when combined with a sound logging strategy.


toString() methods can be costly to invoke, so it's important to ensure that they're not invoked unnecessarily (for example, by the generation of logging messages that won't be output).

Eliminate code duplication.

Code duplication is disastrous for maintenance and usually reflects time wasted in development.
Continually strive to eliminate code duplication.



Don't publicly expose untyped collections where an array could be used.

Helps make code self-documenting and removes one possibility of incorrect usage.
Avoids expensive, error-prone, type casts.

Sometimes converting data to an array type is awkward or slow, or we require a collection (for example, to enable lazy materialization).

Neutral. If it's slower to convert a collection into an array, it's probably not a good idea to use this approach.

Document code thoroughly

Code that isn't throroughly documented is unfinished and potentially useless. The standard Javadoc tool is the cornerstone of our documentation strategy.



Instrument code with logging output.

Enormously helpful during debugging and maintenance.
Can be helpful to staff administering a running app.

None, if logging is implemented properly.

Careless implementation of logging, or misconfiguration of a logging system, may reduce performance. However, this can be avoided by generating log messages only if we know they'll be displayed.

In the next chapter we'll move from the theoretical to the practical, looking at the business requirements for the sample app that we'll discuss throughout the rest of this tutorial.