To ensure flexible package relationships, Java developers strive to depend on the abstract part of a package and minimize dependencies on the concrete parts. By doing so, we minimize the impact of change, resulting in a higher degree of architectural integrity. We’ll explore some heuristics that help ensure a system’s package relationships are flexible and explore how these heuristics encourage the creation of software architectures exhibiting high degrees of resiliency and maintainability.
Packages must be willing to change
Packages that are heavily depended upon compromise architectural integrity because any change to the contents of that package can affect other areas of the system that depend on the same package. In contrast, if a package is not heavily depended upon, the ramifications of change are much less dramatic.
Most Web applications require a logging mechanism that allows the application to track certain error conditions or help with debugging. The logging mechanism will likely be a prepackaged component, such as Log4j from Apache, or the new logging facilities in Java 2 v.1.4. Regardless of the mechanism employed, a heavy dependence on the logging package throughout the application introduces risk any time the contents of the logging package are changed.
Because so many other packages in the system depend on the logging package, it’s important to minimize the likelihood of change to it. One way to do that is to restrict such changes to only those in implementation that don’t affect client packages. Since we’re using an external logging facility that we can’t control, we need an approach to help minimize the impact of change on the logging facility. A common approach to insulating the application from changes in underlying implementation is to use the facade pattern. Figure A illustrates a LoggingFacade class packaged separately in logfacade.
|The LoggingFacade class|
This facade helps insulate the application from changes to the logging implementation. All application dependencies on logging should be forced through the logfacade. While the logging implementation may require frequent change or upgrades, these changes are well insulated, and no other application components have a heavy dependence on the logging package.
The LoggingFacade class, when packaged separately in its logfacade package, supports each of the following heuristics from the perspective of the logging implementation:
- Packages least likely to change should be most heavily depended upon.
- Packages most likely to change should be least heavily depended upon.
However, the logfacade package is still volatile in that changes can disrupt application components using it. If we replace our existing logging component, or possibly offer another logging component as an option, the insulation provided by the logfacade may be compromised. It must be changed to support the new logging implementation. Because the logfacade package is widely used throughout the application, we must ensure this package is as resilient as possible. Abstraction is the answer.
Abstraction focuses on important characteristics of elements
Abstraction is a fundamental principle of object orientation. Abstraction can be a bit confusing because it often refers to both a process and the artifacts resulting from that process. When performing the process of abstraction, focus on identifying the important characteristics of an element while ignoring less important characteristics. When applied correctly, the resulting artifacts represent the abstractions that outwardly exhibit the essential characteristics of those elements while suppressing those characteristics that are less essential. One of the most beneficial ways to realize the power of abstraction when designing packages is to separate the responsibilities of a package from the implementation. In other words, separate what something does, called the specification, from how it actually does it, called the implementation.
Inheritance and polymorphism are frequently used to separate the specification from the implementation in object orientation. By defining abstract classes, we can inherit from these abstractions and provide specialized implementation. This allows us to effectively couple classes to an interface but not to an implementation. The following heuristic applies this concept to packages: Packages most heavily depended upon should be as abstract as possible.
Abstract packages consist primarily of abstract classes or interfaces. Depending upon abstract classes or interfaces instead of an underlying implementation allows us to modify the underlying implementation without affecting the specification. For packages that are heavily depended upon, you should ensure that those dependencies are on abstract components, not concrete components. Depending upon the abstract components helps increase architectural resiliency.
In our logging example discussed earlier, depending directly on the logging package couples us to the implementation provided by that package. Introducing the logfacade package mitigated the risk of change to logging. However, this risk was only transferred to the LoggingFacade class, not actually eliminated. To further minimize the impact of change, we introduce a LoggerInterface in the logfacade package. The LoggingFacade implements this LoggerInterface. Figure B illustrates the new LoggerInterface, expanding on the original design seen in Figure A by also showing how we can provide support for the new logging facility in Java 2 v.1.4.
|The new LoggerInterface class|
Key considerations for package design
Here are the key considerations for designing highly flexible packages:
- Object Creation—Creating concrete classes couples us directly to those classes. To effectively limit the coupling between packages, object factories can be used to create the concrete classes contained inside packages. These factories return references to the interface data type.
- Abstract Packages—Because abstract packages provide only a specification to which other packages are bound, abstract packages that have few other packages dependent upon them are virtually useless.
- Degree of abstractness—It’s unlikely that we’ll create packages that consist entirely of abstract or interface classes. Instead, packages will likely be a mix of both abstract and concrete classes. In these situations, it’s important to ensure we depend only on the abstract portions of a package.
Learn more about Java design
Read the book Java Design: Objects, UML and Process for more information.
Packaged and ready to go
In the previous article, we discussed the importance of designing package relationships and their impact on architectural integrity. This installment focused on abstract packages that can minimize dependencies and help ensure flexible package relationships. The heuristics we examined will help you design resilient and easily maintained architectures.