Applied Software Engineering Using Apache Jakarta Commons (Charles River Media Computer Engineering)
The Commons Bridge best practice is very similar to the bridge pattern as described in the Design Patterns, Elements of Reusable Object Oriented Software book. Both are used to separate intention from implementation. The difference between the Commons Bridge and the bridge pattern is implementation and variation. The purpose of the original bridge pattern was to separate an intention from implementation. The intention is the signature of the interface, and the implementation is a class that inherits the interface and implements the interface signature. The consumer of the Commons Bridge interacts with the interface. This frees the class that inherits the interface to change implementation details or even change the name of the class, without interfering with the client.
The Commons Bridge was created (instead of directly referencing the bridge pattern) because the bridge pattern is focused on one context. In reality, the concept of the bridge is very powerful because of its separation of intention and implementation. The problem is that the different solutions can be applied and must be applied depending on the context. In this book, all the examples are from the Apache projects, so it is only appropriate that the name used is Commons Bridge. In the simplest case, the Commons Bridge is implemented as shown in Listing 2.7.
Listing 2.7
|
package example; package org.apache.commons.cache; public interface Cache extends Serializable { // ... } package org.apache.commons.cache; public abstract class BaseCache implements Cache { // ... }
|
In Listing 2.7, the base interface Cache is defined. The abstract class BaseCache subclasses the class Cache . However, neither Cache nor BaseCache can be instantiated , because Cache is an interface and BaseCache is an abstract class. Notice that both the class and interface reside in the same Java package. This is on purpose because it indicates a package ”this is intended for external use or is a core package for the component or application.
A common implementation technique in the Jakarta projects is to combine the use of an interface and an abstract class. The interface defines an intention, but the abstract class defines a default implementation. Consider Listing 2.8, which shows a more complete implementation of the Commons Bridge.
Listing 2.8
|
package com.devspace.jseng.granularization; interface BaseInterface { void method(); } abstract class DefaultFunctionality implements BaseInterface { public void method() { System.out.println( "Default functionality"); } } class UserImplementation extends DefaultFunctionality { public void method() { System.out.println( "Overridden functionality"); } }
|
The interface BaseInterface defines an interface, which is then subclassed by the abstract class DefaultFunctionality . The abstract class DefaultFunctionality defines an implementation of the method method , which is a default functionality. When the UserImplementation class extends DefaultFunctionality , UserImplementation does not need to implement the method method . It is the choice of the class UserImplementation . This allows the UserImplementation class to implement the methods it needs to, when it needs to.
Often, an implementation may need to override only specific pieces of functionality, not the entire interface. The technique of using both an interface and an abstract is not used everywhere in the Jakarta projects. The decision depends on whether or not each implementation should implement all methods of the interfaces. A case where not all methods should be implemented is when the interface exposes structural methods, as shown in Listing 2.9.
Listing 2.9
|
interface Callback { void addListener( BaseInterface interf); void deleteListener( BaseInterface interf); void method(); }
|
In Listing 2.9, the interface Callback has three methods, but two are considered structural. The methods addListener and deleteListener are structural because they define methods used by a specific implementation to execute other code. A simpler indicator is if the methods, when implemented, appear similar in all of the implementations . In Listing 2.9, the method addListener would add an interface instance to some collection. Regardless of the implementation, most likely all implementations would use a collection. You could consider the structural methods as those that perform generic operations on other interfaces or classes.
A structural class or interface is a programming construct where the specified construct is not used to execute any programming logic related to the problem to be solved . The purpose of the structural class or interface is to organize another class or interface for later use in some kind of programming logic. An example of a structural class or interface is the management of an array of interface implementations. Typically, most structural classes are implemented as abstract classes that need to be subclassed.
In Listing 2.9, the structural methods would operate on the interface BaseInterface , which was shown in Listing 2.8. Therefore, any class that implements the Callback interface will need to implement the structural functionality. If you want to simplify the structural functionality, use an abstract class to implement the basics; then, the client class that subclasses the abstract class finishes the implementation. Therefore, the rule of thumb should be that an abstract class provides an infrastructure defined by the interface the client-derived class utilizes. Look back to Listing 2.7; the Cache code in the Jakarta Commons code is doing just that. If you inspect the Jakarta sources, you'll see that this practice of creating a structural abstract class is commonly used and is labeled with the word Base or Skeleton in the class identifier.
Variation: Local Utilization
There is a variation of the Commons Bridge where the implementation of Commons Bridge is used locally. A local utilization happens when a defined interface and defined class are within the same package or same component. In this variation, the Commons Bridge does not have an interface definition, only an abstract class definition. The abstract class still serves the same purpose of defining structural code. However, because the client is local, the interface is not used. This is so because using an interface in the specific situation would be overkill. An interface defines a general intention and is good for general high-level contracts between components and applications. The problem with using interfaces everywhere is that it becomes tedious . When an abstract class is used locally, it defines both the interface and default structural code.
Variation: Private Implementation
Another variation of the Commons Bridge is to make the various user implementations of the abstract class or interface a private class. This variation is represented in the Commons Project vfs , which is a virtual file system class library. From the status document, the purpose of the vfs project is: "Commons VFS provides a single API for accessing various different file systems. It presents a uniform view of the files from various different sources, such as the files on local disk, on an HTTP server, or inside a Zip archive."
A standard API will be defined from the status document, but most likely the user of the API will never want to implement the API. Instead, the user of the API will want to use a specific implementation, which could be a Zip file or Tar file (both are archives that reference a collection of files as a single file). As a result of the vfs API's, the user should not, under any circumstances, have access to the individual implementations. The implementation of this variation is shown in Listing 2.10.
Listing 2.10
|
public interface FileObject { } public abstract class AbstractFileObject implements FileObject { } final class FtpFileObject extends AbstractFileObject { }
|
In Listing 2.10, the interface FileObject has been declared as the API that the external application or component is using. The class AbstractFileObject implements FileObject, and the class FtpFileObject extends AbstractFileObject. Both the interface FileObject and class AbstractFileObject are defined as public , which means that other classes can publicly share both the class and interface. However, the class FtpFileObject is private to the package and final. This implies that the class FtpFileObject cannot be externally instantiated or extended.
The reason for keeping the class FtpFileObject private and final relates to the castle metaphor we discussed earlier in the chapter. The key is to control which people can get into the castle and become part of the community. In the case of the Commons Bridge, the instantiation of the structure is the responsibility of the Commons Bridge, not the client. This ensures that the Commons Bridge has a certain amount of control that only specific implementations can be used in the context of the package. The exact technique of instantiating the implementation is explained in Chapter 3.
Variation: Nested Classes
When you look through the Commons sources you'll see another variation of the Commons Bridge that is used quite often. In this variation, the protected class FtpFileObject is replaced with a nested class; this is shown in the Commons Project lang , which is a project for Java language extensions that did not make the runtime as distributed by Sun Microsystems. Listing 2.11 shows an example of the variation.
Listing 2.11
|
public class FactoryUtils { private static class PrototypeCloneFactory implements Factory, Serializable { } }
|
The class PrototypeCloneFactory has the same role as the class FtpFileObject, and the interface Factory has the same role as the interface FileObject. In this example of the Commons Bridge variation, however, there is no counterpart for the class AbstractFileObject. This is unnecessary because it lacks structural code or methods. The difference in this class declaration is that the user implementation class PrototypeCloneFactory is private and cannot be extended or instantiated by any class other than FactoryUtils.
This strategy using nested classes is very useful when you're implementing small blocks of varying functionality. However, implementing interfaces with many methods will cause the editor to be stressed since an implementation class could easily extend into thousands of lines of code. As in Listing 2.11, there most likely will be no abstract class of default functionality. If you do define an abstract class, ensure that there is a very good reason; otherwise , the line count will increase exponentially. If an abstract class is defined as being structural code, then declare the abstract class as a nested class in the same parent class as the implementations.
Категории